Integrazione di Rails con JS poco chiara

Ciao a tutti,
ho bisogno di un aiuto per capire come il codice javascript si integri
in un’app Rails, ovvero quali sono le best practices al proposito. In
rete non ho trovato risposte soddisfacenti alle mie perplessità.

Prima dell’avvento di UJS il codice js era inserito all’interno delle
viste, ora viene collocato in app/assets/javascript/. Suppongo di avere
una semplice azione invocata dal click su un elemento grafico (uso
codice coffeescript per semplicità):

$("#dropdown").change( ->

)

Questo frammento viene salvato in books.js.coffee che viene caricato
assieme a tutti i file specificati in application.js. Tuttavia l’azione
riportata è visibile e disponibile a tutta l’applicazione e non, come
succede con gli helper, alle sole viste di books, e questa incoerenza a
me non piace molto. Inoltre porta a rischi di collisioni tra nomi (devo
fare attenzione ai selettori che uso).

Procedo con un esempio. Suppongo di avere due liste dropdown: il valore
della prima lista determina il caricamento della seconda lista (es.
regioni e provincie italiane).
Ogni volta che l’utente seleziona un elemento diverso dalla prima lista,
viene invocata una funzione js ‘region_changed’ che effettua una
chiamata ajax che provvede a popolare gli elementi della seconda lista
dropdown:

$("#regione_id").change(function() {
region_changed();
})

Definisco la mia funzione in pippo.js.coffee. Questa funzione dovrà
essere chiamata anche al completamento del caricamento della pagina web.
Che faccio? Inserisco un bel pezzo di codice ‘obtrusive’ tipo

nella mia vista?
E se uso haml per le viste come faccio?

Inoltre: la mia funzione va definita globale (@region_changed) per
essere chiamata dalla vista?
E se voglio usare un helper di routing dentro la funzione per l’URL
della chiama Ajax? Come faccio?
Mi spiego meglio, nel vecchio mondo obtrusive avrei scritto nella vista
(dentro la funzione ‘region_changed’):

$.get(’<%= province_of_region_path %>’, { region_id: id },
function(data) {

Ora che la funzione va messa in app/assets/javascript come si inietta
l’URL giusto?

Troppe domande? Troppa confusione mia?
Sono ben accolti suggerimenti di lettura di articoli o blog…

Grazie mille per ogni spiegazione o suggerimento,

ciao

iwan

Parto dicendo che non sono un esperto di client side scripting :slight_smile:

Pero’ sul progetto che lavoro ora:

  1. evitare che lo script sia usato in tutte le pagine – noi definiamo
    il comportamento del tuo js in funzioni, poi dentro la view
    interessata abbiamo uno script tag che le triggera.

Dato un Game che has_one Display, dentro un un form partial:

app/views/name_space/admin/games/display/_form.html.erb

noi abbiamo:

<script>
$(function() {
  var displayForm = new

NameSpace.Admin.Games.Display.Form(’#display_properties’,
#validateDisplay’, ‘#result’);
});

quella funzione e’ dentro
app/assets/name_space/admin/games/display/form.js perche’ e’ una
comportamento specifico che e’ solo associata a quella view.
Potenzialmente gli dai un nome generico e riusi la chiamata in altre
view, ma sempre tramite uno script tag (diretto o spingendo in
via content_for :head).

1.1) certe librerie che non vogliamo proprio fare impacchettare non
sono incluse nella lista della asset pipeline ed appaiono come
javascript_tag in una content_for head solo nelle view.

<% content_for :head do %>
<%= javascript_include_tag ‘name_space/v1_0/our_library_name’ %>

Questo approccio (ed altri) li abbiamo usati dopo aver visto: http://railsapps.github.com/rails-javascript-include-external.html 2) mixare lo script con codice Ruby -- praticamente quello che viene via asset pipeline puo' esserre erb code. Quindi puoi fare robe tipo: # Rename countdown_event_name.js → countdown_event_name.js.erb var max = <%= Event::NAME_MAX_LENGTH %> Preso pari pari da http://robots.thoughtbot.com/post/32392361476/dont-repeat-your-ruby-constants-in-javascript HTH, Enrico 2012/10/18 Iwan B. : > $("#dropdown").change( -> > Procedo con un esempio. Suppongo di avere due liste dropdown: il valore > > nella mia vista? > function(data) { > ciao > > iwan > > -- > Posted via http://www.ruby-forum.com/. > _______________________________________________ > Ml mailing list > [email protected] > http://lists.ruby-it.org/mailman/listinfo/ml -- Enrico T. Software development and web design currently working @ http://abc.com.au Sydney, NSW, Australia [email protected] mobile (AU) +00610416748450 http://teotti.com

2012/10/18 Enrico T. [email protected]
[cut]

  1. evitare che lo script sia usato in tutte le pagine – noi definiamo
    il comportamento del tuo js in funzioni, poi dentro la view
    interessata abbiamo uno script tag che le triggera.

Non sono tanto d’accordo con questo approccio. Ci sono alcuni casi in
cui
il caricamento eterogeneo di script ti permette di ottenere un guadagno
prestazionale e in quei casi, vai gi di content_for come suggerisci.

Caricare in ogni pagina tutti gli script non per cos assurdo:

  • maggiore semplicit gestionale (eviti di dover specificare i
    content_for)
  • il complessivo js compresso viene caricato e cachato con la prima
    richiesta e reso disponibile per le successive
  • l’overhead computazionale pu non essere rilevante
  • eviti il name clashing semplicemente cambiando la mentalit di gestione
    del DOM

Premessa: scopo del JS aggiungere behavior al contenuto delle pagine.

$(selector).click → doSomething() ha senso se ogni elemento che
corrisponde a selector necessita del behavior “on click do something”.

HTML e JS dovrebbero essere scritti in un’ottica modulare.

Nel caso del JS, potresti creare una classe CoffeeScript deputata alla
gestione dei country/region/province/etc, sempre caricata nell’asset
pipeline definita pi o meno cos:

/app/assets/javascripts/geo_selection.js.coffee:

class GeoSelection
constructor: →
# do something

setup: →
# on some items do .change → …

$ →
if pageNeedGeoSelection
new GeoSelection()

In questo modo l’unico overhead computazionale introdotto da questo
modulo,
per ogni pagina, consiste nel semplice check di una condizione
(verosimilmente la presenza o meno di alcuni elementi nel DOM).

Un buon approccio per evitare il name clashing consiste nel basarsi
sull’uso dei data-*:

in soldoni inserisci l’attributo data-country-region in ogni sezione a
cui
vuoi applicare il comportamento di selezione della localit e nel modulo
CoffeeScript la condizione di applicazione del comportamento sar:

$ →
collection = $(‘[data-country-region]’)
if collection.size() > 0
new GeoSelection(collection)

Maurizio

My profile https://plus.google.com/100973969013103507046/about

2012/10/18 David W. [email protected]

Consiglio anche a te l’approccio data-* driven :wink:

Maurizio

My profile https://plus.google.com/100973969013103507046/about

Ciao a tutti,
ho bisogno di un aiuto per capire come il codice javascript si integri
in un’app Rails, ovvero quali sono le best practices al proposito. In
rete non ho trovato risposte soddisfacenti alle mie perplessit.

Neanche io, a dire la verita. E un’ottima domanda.

Hai ragione, a mio avviso che il pacchettone ‘include tutto’ non e bellissimo, perche ti costringe a scrivere codice ragionando sul
codice in tutte le altre view.

Quello che faccio io e` questo:

  <%= page_specific_javascript -%>

in views/layouts/application.html.erb

def page_specific_javascript
controller = params[:controller]
action = params[:action]

if File.exist?(Rails.root + 

“public/javascripts/#{controller}/#{action}.js”)
javascript_include_tag “/javascripts/#{controller}/#{action}.js”
end
end

in helpers/application_helper.rb

Non mi sembra una soluzione perfetta, ma almeno mi permette di
divedere bene il codice, che per me e piu importante di ottimizzare
i tempi di caricamento al 100%.


David N. Welton

http://www.welton.it/davidw/

http://www.dedasys.com/

Ciao,
io di solito per organizzare il javascript uso un approccio simile a
questo:

Con Rails 3.1 e` stata introdotta la asset pipeline. CSS e JS vengono
compilati in un unico file, viene minified (non son sicuro di come si
dica in italiano) e gli viene applicato anche un digest alla fine tipo
?3343343 per forzare il browser quando il file vengono cambiati. Con la
asset pipeline la maggior parte dei problemi dello sviluppo CSS e JS
viene rimosso. Mettere JS nelle view e helper non mi sembra molto ‘the
Rails Way’, almeno dalla versione 3.1 in poi.

Purtroppo Rails eun server-side web framework, ed usarlo per il front-end, implica utilizzare dei compromessi. Quindi il modo di default al momento e avere un file pages.js.coffee per il controller pages,
quindi mappare il controller/view con un file coffeescript. Poi tutti i
file verranno messi insieme, ‘minificati’, applicato un digest e verra`
servito solamente application.js.

Io cerco si unire il codice per le funzioni che fornisce. Tipo
valiables.js.coffee per delle variabili, tipo il formato dell’ora, poi
metto il codice specifico al controller nel suo corrispettivo file
coffee.

JQuery aggiunge delle callback agli elementi del DOM, se non trova
l’elemento si vede che sei nel controller images piuttosto che pages, ma
tanto non accade nessun errore JS. Quindi avere la partet front-end in
un file application.js va piu` che bene.

Se poi l’applicazione diventa molto basata sul JS, allora emeglio usare un JS framework tipo Backbone, Spine o Ember ed usare Rails solamente per fornire i dati via REST. Questo scenario lo trovo ostico e complesso. A questo punto preferisco Meteor Framework, dove l'applicazione viene scritta server e client side con il JavaScript ed il codice client side o server side e separato in questo modo:

// On server startup, if the database is empty, create some initial
data.
if (Meteor.isServer) {
Meteor.startup(function () {
if (Rooms.find().count() === 0) {
Rooms.insert({name: “Initial room”});
}
});
}

e Meteor.isClient per la parte client.

In un mondo ideale, dove con Ruby si potrebbe fare tutto, la parte
client sarebbe scritta in Ruby e compilata in JavaSCript, come avviene
per CoffeeScript e la parte server utilizzerebbe semplicemente Ruby.

Quindi una risposta definitiva all tua risposta credo che non ci sia. Se
hai poco JavaScript usalo come prescrive la asset pipeline, se ne hai
tanto passa a Ember.js oppure a Meteor e lasci Rails. Oppure scrivi un
equivalente di meteor con Ruby, incluso la compilazione di JavaSCript da
codice Ruby

Ciao,
io di solito per organizzare il javascript uso un approccio simile a
questo:

Questa mi sembra la risposta migliore e piu` pulita di quelle che ho
visto finora.

Quello che non mi piace di “data-blah” e che se inizi ad avere diverse pagine con codice diverso per ogni pagina inizia a molteplicarsi il lavoro: pagina X ha del codice associato che cerca il suo data-foobar, ma lo stesso codice cerchera data-foobar su pagine Y
e Z, mentre il codice per quelle pagine cerchera data-y e data-z nelle altre dove non c'e, e cosi` via. O sbaglio? Forse non ha un
costo alto, ma comunque non mi sembra bellissimo.

La cosa strana di questa situazione e che normalmente quelli di Rails sono molto bravi a suggerire "the best way to do it", ma in questo caso non c'e molto nella documentazione.


David N. Welton

http://www.welton.it/davidw/

http://www.dedasys.com/

On 19/ott/2012, at 10:41, David W. wrote:

Ciao,
io di solito per organizzare il javascript uso un approccio simile a
questo:

Questa mi sembra la risposta migliore e piu` pulita di quelle che ho
visto finora.

Sono pienamente d’accordo, mi sembra un approccio molto elegante e
pulito, ed quello che sto provando :slight_smile:

Lo voglio provare anch’io, grazie David.

2012/10/19 David W. [email protected]

diverse pagine con codice diverso per ogni pagina inizia a
molteplicarsi il lavoro: pagina X ha del codice associato che cerca il
suo data-foobar, ma lo stesso codice cerchera data-foobar su pagine Y e Z, mentre il codice per quelle pagine cerchera data-y e data-z
nelle altre dove non c’e, e cosi via. O sbaglio? Forse non ha un
costo alto, ma comunque non mi sembra bellissimo.

E’ vero che al document ready verranno effettuati n controlli di
esistenza
di elementi contenenti data-X, ma nella maggior parte dei casi mi paiono
prestazionalmente irrilevanti.

D’altro canto hai ottenuto il vantaggio di indebolire l’accoppiamento
tra
sorgenti ruby e sorgenti js.

Nel caso del link postato, l’associazione Controller ↔ Componente JS
pu
avere senso in qualche use case ma non va nella direzione “corretta”
(non
parlo di Rails way, parlo di “buon senso” :-)).

In genere dovremmo ragionare per blocchi funzionali e far interagire
solo
i blocchi funzionali affini tra di loro, per i soliti concetti di
riutilizzo, blah blah blah :slight_smile:

Maurizio

My profile https://plus.google.com/100973969013103507046/about

Maurizio De magnis wrote in post #1080430:

Nel caso del link postato, l’associazione Controller <-> Componente JS
pu
avere senso in qualche use case ma non va nella direzione “corretta”
(non
parlo di Rails way, parlo di “buon senso” :-)).

In genere dovremmo ragionare per blocchi funzionali e far interagire
solo
i blocchi funzionali affini tra di loro, per i soliti concetti di
riutilizzo, blah blah blah :slight_smile:

Beh, niente ti impedisce di creare oggetti specifici per alcune funzioni
dell’applicazione e richiamarli all’interno delle varie azioni legate ai
“controller” javascript. Di solito faccio così.
Ad esempio il BlahBlahController potrebbe avere un metodo new con
new ->
App.TooltipManager.addTooltip(‘myElement’, “ciao, sono un tooltip”);
App.DateManager.myDateConstraint(‘dateElement’)
// o (new App.DateManager(‘dateElement’)).myDateConstraint() se
preferite gli oggetti :slight_smile:

Le cose belle di quest’approccio sono, secondo me: il fatto di sapere
immediatamente dove si trova il javascript relativo ad una pagina; il
fatto di isolare il javascript, evitando che per errore partano funzioni
che interferiscano con il corretto funzionamento dell’applicazione.

2012/10/19 Marco S. [email protected]

solo
App.DateManager.myDateConstraint(‘dateElement’)
// o (new App.DateManager(‘dateElement’)).myDateConstraint() se
preferite gli oggetti :slight_smile:

Le cose belle di quest’approccio sono, secondo me: il fatto di sapere
immediatamente dove si trova il javascript relativo ad una pagina; il
fatto di isolare il javascript, evitando che per errore partano funzioni
che interferiscano con il corretto funzionamento dell’applicazione.

Ok, stai limitando gli “svantaggi” dell’associazione controller ↔ js,
limitando la responsabilit del js bindato con il controller al mero
instradamento :slight_smile:
Rimane comunque la “noia” dell’update di questo mapping nell’eventualit
che un componente JS venga usato in pi controller o qualora dovesse
sorgere la necessit di spostare l’html “bersaglio” al di fuori del
raggio
d’azione del controller.
E’ un’associazione a mio parere pericolosa in casi di siti molto grossi.

Ma sottolineo che se lo use case composto da pochi controller, questa
associazione resta uno dei compromessi migliori :slight_smile:

Maurizio

My profile https://plus.google.com/100973969013103507046/about

Maurizio De magnis wrote in post #1080455:

2012/10/19 Marco S. [email protected]

solo
App.DateManager.myDateConstraint(‘dateElement’)
// o (new App.DateManager(‘dateElement’)).myDateConstraint() se
preferite gli oggetti :slight_smile:

Le cose belle di quest’approccio sono, secondo me: il fatto di sapere
immediatamente dove si trova il javascript relativo ad una pagina; il
fatto di isolare il javascript, evitando che per errore partano funzioni
che interferiscano con il corretto funzionamento dell’applicazione.

Ok, stai limitando gli “svantaggi” dell’associazione controller ↔ js,
limitando la responsabilit del js bindato con il controller al mero
instradamento :slight_smile:
Rimane comunque la “noia” dell’update di questo mapping nell’eventualit
che un componente JS venga usato in pi controller o qualora dovesse
sorgere la necessit di spostare l’html “bersaglio” al di fuori del
raggio
d’azione del controller.
E’ un’associazione a mio parere pericolosa in casi di siti molto grossi.

Secondo me e` comodo soprattutto quando hai molto codice JS, quindi se
hai siti grossi od una app con molto JS. Trovo soprattutto importante
l’utilizzo della variabile App, che contiene tutto il codice JS del sito
per evitare collisioni con altro codice, ma anche per il suo approccio
OOP.

Io lo vedo come i controller di Rails, quindi serve ad organizzare il
codice.

Delle volte mi e successo di scrivere un matcher jQuery per una pagina, poi mi son reso conto che lo stesso codice faceva il matching con un elemento del DOM in un'altra pagina, ed in quella pagina invece non era una funzionalita desiderata. Quindi mi torna il discorso “il fatto di
isolare il javascript, evitando che per errore partano funzioni che
interferiscano con il corretto funzionamento dell’applicazione.”

Ma sottolineo che se lo use case composto da pochi controller, questa
associazione resta uno dei compromessi migliori :slight_smile:

Maurizio

My profile https://plus.google.com/100973969013103507046/about