Forum: Italian Ruby user group Integrazione di Rails con JS poco chiara.

Posted by Iwan B. (1w4n)
on 2012-10-18 00:15
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

<script type="text/javascript" charset="utf-8">
   $(document).ready(function() {
      region_changed();
   })
</script>

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
Posted by Enrico T. (enrico_t)
on 2012-10-18 04:14
(Received via mailing list)
Parto dicendo che non sono un esperto di client side scripting :)

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');
    });
    </script>

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 <head>
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' %>
  <script type="text/javascript">

Questo approccio (ed altri) li abbiamo usati dopo aver visto:
http://railsapps.github.com/rails-javascript-inclu...


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...



HTH,
Enrico

2012/10/18 Iwan B. <iwan.buetti@mac.com>:
> $("#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
> Ml@lists.ruby-it.org
> http://lists.ruby-it.org/mailman/listinfo/ml



--
Enrico Teotti
Software development and web design
currently working @ http://abc.com.au
Sydney, NSW, Australia
enrico.teotti@gmail.com
mobile (AU) +00610416748450

http://teotti.com
Posted by Maurizio De magnis (olistik)
on 2012-10-18 09:51
(Received via mailing list)
2012/10/18 Enrico Teotti <enrico.teotti@gmail.com>
[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-*:

<select id="region_id" data-country-region="true">

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>
Posted by David Welton (Guest)
on 2012-10-18 10:18
(Received via mailing list)
> 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/
Posted by Maurizio De magnis (olistik)
on 2012-10-18 10:24
(Received via mailing list)
2012/10/18 David Welton <davidnwelton@gmail.com>

>
Consiglio anche a te l'approccio data-* driven ;-)

Maurizio
--
My profile <https://plus.google.com/100973969013103507046/about>
Posted by Marco S. (marco_s)
on 2012-10-18 11:21
Ciao,
io di solito per organizzare il javascript uso un approccio simile a 
questo:
http://blog.jerodsanto.net/2012/02/a-simple-patter...
Posted by Riccardo Tacconi (rtacconi)
on 2012-10-19 09:45
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 e` un 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 e` meglio 
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
Posted by David Welton (Guest)
on 2012-10-19 10:41
(Received via mailing list)
> Ciao,
> io di solito per organizzare il javascript uso un approccio simile a
> questo:
> 
http://blog.jerodsanto.net/2012/02/a-simple-patter...

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/
Posted by Iwan Buetti (Guest)
on 2012-10-19 11:06
(Received via mailing list)
On 19/ott/2012, at 10:41, David Welton wrote:

>> Ciao,
>> io di solito per organizzare il javascript uso un approccio simile a
>> questo:
>> 
http://blog.jerodsanto.net/2012/02/a-simple-patter...
>
> 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 :-)
Posted by Riccardo Tacconi (rtacconi)
on 2012-10-19 12:05
Lo voglio provare anch'io, grazie David.
Posted by Maurizio De magnis (olistik)
on 2012-10-19 12:06
(Received via mailing list)
2012/10/19 David Welton <davidnwelton@gmail.com>

> 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 :-)

Maurizio
--
My profile <https://plus.google.com/100973969013103507046/about>
Posted by Marco S. (marco_s)
on 2012-10-19 12:47
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 :-)
>

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 :-)

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.
Posted by Maurizio De magnis (olistik)
on 2012-10-19 16:15
(Received via mailing list)
2012/10/19 Marco S. <marco.sormani@gmail.com>

> > solo
>   App.DateManager.myDateConstraint('dateElement')
>   // o (new App.DateManager('dateElement')).myDateConstraint() se
> preferite gli oggetti :-)
>
> 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 :-)
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 :-)

Maurizio
--
My profile <https://plus.google.com/100973969013103507046/about>
Posted by Riccardo Tacconi (rtacconi)
on 2012-10-19 16:42
Maurizio De magnis wrote in post #1080455:
> 2012/10/19 Marco S. <marco.sormani@gmail.com>
>
>> > solo
>>   App.DateManager.myDateConstraint('dateElement')
>>   // o (new App.DateManager('dateElement')).myDateConstraint() se
>> preferite gli oggetti :-)
>>
>> 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 :-)
> 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 :-)
>
> Maurizio
> --
> My profile <https://plus.google.com/100973969013103507046/about>
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.