Avec Rails 3.1, où placez-vous votre code JavaScript «spécifique à la page»?

388

À ma connaissance, tout votre JavaScript est fusionné en un seul fichier. Rails le fait par défaut lorsqu'il ajoute //= require_tree .au bas de votre application.jsfichier manifeste.

Cela ressemble à un vrai sauveteur, mais je suis un peu préoccupé par le code JavaScript spécifique à la page. Ce code est-il exécuté sur chaque page? La dernière chose que je veux, c'est que tous mes objets soient instanciés pour chaque page alors qu'ils ne sont nécessaires que sur 1 page.

De plus, n'y a-t-il pas un potentiel de code qui se heurte aussi?

Ou placez-vous une petite scriptbalise au bas de la page qui appelle simplement une méthode qui exécute le code javascript de la page?

Vous n'avez donc plus besoin de require.js?

Merci

EDIT : J'apprécie toutes les réponses ... et je ne pense pas qu'ils s'attaquent vraiment au problème. Certains d'entre eux concernent le style et ne semblent pas se rapporter ... et d'autres mentionnent simplement javascript_include_tag... qui je sais existe (évidemment ...) mais il semblerait que la voie à suivre pour Rails 3.1 consiste à récapituler tout votre JavaScript dans 1 fichier plutôt que de charger du JavaScript individuel au bas de chaque page.

La meilleure solution que je puisse trouver consiste à envelopper certaines fonctionnalités dans des divbalises avec ids ou classes. Dans le code JavaScript, vous vérifiez simplement si le idou se classtrouve sur la page, et si c'est le cas, vous exécutez le code JavaScript qui lui est associé. De cette façon, si l'élément dynamique n'est pas sur la page, le code JavaScript ne s'exécute pas - même s'il a été inclus dans le application.jsfichier massif emballé par Sprockets.

Ma solution ci-dessus a l'avantage que si un champ de recherche est inclus sur 8 des 100 pages, il ne fonctionnera que sur ces 8 pages. Vous n'aurez pas non plus à inclure le même code sur 8 des pages du site. En fait, vous n'aurez plus jamais à inclure de balises de script manuelles sur votre site.

Je pense que c'est la réponse à ma question.

Emblème du feu
la source
11
"La façon dont Rails 3.1 va de l'avant est d'envelopper tout votre Javascript dans un fichier plutôt que de charger du Javascript individuel au bas de chaque page." - Seulement parce que l'équipe principale de Rails est, et a toujours été, vraiment mauvaise à savoir comment pour gérer JavaScript. Les petits fichiers sont généralement meilleurs (voir mes commentaires ailleurs). En ce qui concerne JavaScript, la méthode Rails est rarement la bonne (sauf pour le pipeline d'actifs, qui donne un coup de pied au cul, et l'encouragement de CoffeeScript).
Marnen Laibow-Koser
Vous allez donc inclure vos fichiers js spécifiques à chaque page sur chaque page? Je pense que c'est un gaspillage, je suis plus d'accord avec la réponse de ClosureCowboy.
gerky
1
Avez-vous regardé la réponse acceptée pour cette question? stackoverflow.com/questions/6571753/…
rassom
1
@DutGRIFF En d'autres termes: non, il n'est pas préférable de faire les choses à la manière de Rails dans ce cas (ou du moins, ne mettez pas tout application.js), et en fait la référence que vous avez fournie explique pourquoi il en est ainsi: le téléchargement est le partie la plus lente du processus d'exécution JS. De nombreux petits fichiers sont plus faciles à mettre en cache qu'un gros. Les gens de Unholy Rails ne semblent pas réaliser, alors, que leurs recommandations sont incompatibles avec les principes auxquels ils essaient de se conformer, et donc leurs recommandations ne devraient pas être prises au sérieux.
Marnen Laibow-Koser
1
@DutGRIFF Non, un gros fichier JS ne serait normalement pas une bonne chose même une fois mis en cache. Voir mes commentaires ailleurs sur cette page: les petits fichiers peuvent mieux cibler des pages spécifiques et peuvent être mis en cache avec une granularité plus fine. Je ne vois aucun bon cas d'utilisation pour un seul gros fichier, sauf s'il n'y a pas de code spécifique à la page .
Marnen Laibow-Koser le

Réponses:

157

Les documents Asset Pipeline suggèrent comment effectuer JS spécifique au contrôleur:

Par exemple, si un ProjectsControllerest généré, il y aura un nouveau fichier à app/assets/javascripts/projects.js.coffeeet un autre àapp/assets/stylesheets/projects.css.scss . Vous devez mettre tout JavaScript ou CSS unique à un contrôleur dans leurs fichiers de ressources respectifs, car ces fichiers peuvent ensuite être chargés uniquement pour ces contrôleurs avec des lignes telles que <%= javascript_include_tag params[:controller] %>ou <%= stylesheet_link_tag params[:controller] %>.

Lien vers: asset_pipeline

meleyal
la source
50
C'est la façon la plus élégante de le faire. Mais aussi, vous devrez supprimer la ligne // = require_tree. de l'application.js.coffee
zsljulius
2
Je suis totalement d'accord avec cette méthode. Les autres méthodes semblent très maladroites et finissent toujours par charger un fichier js géant. Le projet sur lequel je travaille a presque 2 Mo de fichiers / plugins JS, etc. APRÈS avoir été combinés / minifiés.
Bill Garrison
2
Je suis assez nouveau dans Rails, mais il me semble que cela devrait être le comportement par défaut.
Ross Hambrick
12
Pour un contrôle spécifique à une action, je l'ai dans ma disposition, car chaque action pour chaque contrôleur n'a pas de JS spécifique. page_specific_js = "#{params[:controller]}_#{params[:action]}"et alors; javascript_include_tag page_specific_js if Rails.application.assets.find_asset page_specific_js
Sujimichi
2
Les actions spécifiques au contrôleur sont-elles toujours minifiées? Sont-ils ajoutés au fichier js unique créé par les sprockets, ou cela entraîne-t-il de multiples demandes de fichiers d'actif?
Jason
77

Pour les js spécifiques à une page, vous pouvez utiliser la solution Garber-Irish .

Ainsi, votre dossier javascripts Rails pourrait ressembler à ceci pour deux contrôleurs - voitures et utilisateurs:

javascripts/
├── application.js
├── init.js
├── markup_based_js_execution
├── cars
   ├── init .js
   ├── index.js
   └── ...
└── users
    └── ...

Et les javascripts ressembleront à ceci:

// application.js

//= 
//= require init.js
//= require_tree cars
//= require_tree users

// init.js

SITENAME = new Object();
SITENAME.cars = new Object;
SITENAME.users = new Object;

SITENAME.common.init = function (){
  // Your js code for all pages here
}

// cars/init.js

SITENAME.cars.init = function (){
  // Your js code for the cars controller here
}

// cars/index.js

SITENAME.cars.index = function (){
  // Your js code for the index method of the cars controller
}

et markup_based_js_execution contiendra du code pour l'objet UTIL, et sur l'exécution UTIL.init prête pour DOM.

Et n'oubliez pas de mettre ceci dans votre fichier de mise en page:

<body data-controller="<%= controller_name %>" data-action="<%= action_name %>">

Je pense également qu'il est préférable d'utiliser des classes plutôt que des data-*attributs, pour un meilleur CSS spécifique à la page. Comme Jason Garber l'a mentionné: les sélecteurs CSS spécifiques à une page peuvent devenir très gênants (lorsque vous utilisez des data-*attributs)

J'espère que cela t'aidera.

welldan97
la source
4
Que faire si vous avez besoin d'une variable disponible pour toutes les actions dans le contrôleur des utilisateurs, mais non disponible dans les autres contrôleurs? Cette méthode n'a-t-elle pas des problèmes de portée?
tybro0103
@ tybro0103, je pense que pour implémenter ce comportement, vous souhaitez écrire quelque chose comme window.varForOneController='val'dans cette fonction d'initialisation du contrôleur. Gon gem peut également aider ici ( github.com/gazay/gon ). Il peut y avoir d'autres solutions.
welldan97
1
@ welldan97 Downvoting pas pour votre explication - ce qui est excellent - mais parce que la structure Garber-Irish est mauvaise. Il charge tous vos JS sur chaque page et dépend des classes et des ID de l'élément <body> pour trier les choses. C'est un signe certain de combattre le DOM: dans des circonstances normales, l'élément <body> ne devrait pas avoir besoin d'une classe ou d'un ID, car il n'y en a jamais qu'un dans un document. La bonne façon de procéder consiste simplement à supprimer le //= require_tree .code JavaScript spécifique à la page et à l'utiliser. Si vous essayez activement de ne pas faire cela, alors vous vous efforcez de mauvaises pratiques.
Marnen Laibow-Koser
2
@ MarnenLaibow-Koser Personnellement, je crois que le chargement de tous les js sur chaque page est bon pour la plupart des projets lorsque vous combinez tous les js dans un seul fichier et que vous le minimisez. Je crois que dans l'ensemble, cela fonctionne plus rapidement pour l'utilisateur. Au moins, il ressemble plus à un conflit un fichier js contre plusieurs (c'est-à-dire regardez stackoverflow.com/questions/555696/… ). De plus, il n'y a rien de mal à utiliser des classes et des identifiants sur le corps si cela rend le code plus simple et fonctionne pour vous. Modernizr ( modernizr.com ) le fait, ainsi que d'autres bibliothèques.
welldan97
2
@ MarnenLaibow-Koser le pipeline d'actifs ferroviaires, pour moi, semble être un bon candidat pour la comparaison avec la compilation. Un programmeur écrit son javascript dans de beaux modules découplés, puis il est regroupé, minifié et servi. Tout comme dans le cas des langages compilés, il y aura toujours des programmeurs qui penseront qu'ils ont une longueur d'avance sur le compilateur ... mais je pense que c'est rarement vrai.
Ziggy
65

Je vois que vous avez répondu à votre propre question, mais voici une autre option:

Fondamentalement, vous faites l'hypothèse que

//= require_tree .

est requis. Ce n'est pas. N'hésitez pas à le retirer. Dans mon application actuelle, la première que je fais honnêtement avec 3.1.x, j'ai créé trois fichiers JS de premier niveau différents. Mon application.jsfichier n'a que

//= require jquery
//= require jquery_ujs
//= require_directory .
//= require_directory ./api
//= require_directory ./admin

De cette façon, je peux créer des sous-répertoires, avec leurs propres fichiers JS de niveau supérieur, qui incluent uniquement ce dont j'ai besoin.

Les clés sont:

  1. Vous pouvez supprimer require_tree- Rails vous permet de modifier les hypothèses qu'il fait
  2. Le nom n'a rien de spécial application.js- tout fichier du assets/javascriptsous - répertoire peut inclure des directives de pré-processeur avec//=

J'espère que cela aide et ajoute quelques détails à la réponse de ClosureCowboy.

Sujal

sujal
la source
8
+1 C'est formidable à savoir pour un débutant comme moi. Je lui donnerais +2 si je le pouvais.
jrhorn424
5
@sujal Exactement. L'équipe principale de Rails est connue pour sa gestion JavaScript épouvantable. N'hésitez pas à ignorer leurs suggestions et à utiliser simplement les bonnes parties du portefeuille d'actifs. :)
Marnen Laibow-Koser
1
Merci beaucoup pour ce conseil. Je n'ai pas plusieurs fichiers JS "de haut niveau", selon le module de mon application. Fonctionne bien.
elsurudo
1
+1 Le point important ici pour moi est que vous pouvez remplacer //= require_tree .par //= require_directory .afin que vous puissiez conserver tous les fichiers existants où ils se trouvent et créer de nouveaux répertoires pour les fichiers spécifiques à la page.
zelanix
41

Autre option: pour créer des fichiers spécifiques à une page ou à un modèle, vous pouvez créer des répertoires dans votre assets/javascripts/dossier.

assets/javascripts/global/
assets/javascripts/cupcakes
assets/javascripts/something_else_specific

Votre application.jsfichier manifeste principal pourrait être configuré pour charger ses fichiers à partir de global/. Des pages ou des groupes de pages spécifiques peuvent avoir leurs propres manifestes qui chargent des fichiers à partir de leurs propres répertoires spécifiques. Sprockets combinera automatiquement les fichiers chargés par application.jsavec vos fichiers spécifiques à la page, ce qui permet à cette solution de fonctionner.

Cette technique peut également être utilisée style_sheets/.

FermetureCowboy
la source
13
Tu m'as fait envie de cupcakes maintenant .. Dangit!
Chuck Bergeron
J'aime vraiment cette solution. Le seul problème que j'ai avec cela est que ces manifestes supplémentaires ne sont pas compressés / uglifiés. Ils sont cependant correctement compilés. Y a-t-il une solution ou est-ce que je manque quelque chose?
CLST
1
cela signifie-t-il que le navigateur charge un fichier js, c'est-à-dire une combinaison de fichier global + spécifique à la page?
lulalala
Pourriez-vous jeter un œil à ma question si vous êtes disponible? stackoverflow.com/questions/17055213/…
Maximus S
1
@clst Je pense que c'est la réponse que vous cherchez: guides.rubyonrails.org/asset_pipeline.html#precompiling-assets
FrontierPsycho
23

J'apprécie toutes les réponses ... et je ne pense pas qu'ils s'attaquent vraiment au problème. Certains d'entre eux concernent le style et ne semblent pas se rapporter ... et d'autres mentionnent simplement javascript_include_tag... qui je sais existe (évidemment ...) mais il semblerait que la voie à suivre pour Rails 3.1 consiste à récapituler tout votre Javascript dans 1 fichier plutôt que de charger le Javascript individuel au bas de chaque page.

La meilleure solution que je puisse trouver consiste à envelopper certaines fonctionnalités dans des divbalises avec ids ou classes. Dans le code javascript. Ensuite, vous vérifiez simplement si le idou classest sur la page, et si c'est le cas, vous exécutez le code javascript qui lui est associé. De cette façon, si l'élément dynamique n'est pas sur la page, le code javascript ne s'exécute pas - même s'il a été inclus dans le application.jsfichier massif emballé par Sprockets.

Ma solution ci-dessus a l'avantage que si un champ de recherche est inclus sur 8 des 100 pages, il ne fonctionnera que sur ces 8 pages. Vous n'aurez pas non plus à inclure le même code sur 8 des pages du site. En fait, vous n'aurez plus jamais à inclure de balises de script manuelles sur votre site, sauf pour précharger des données.

Je pense que c'est la réponse à ma question.

Emblème du feu
la source
Mais vous voulez en fait ces <script>balises manuelles . Oui, les classes et les identifiants font partie de la réponse, mais cela n'a aucun sens pour l'utilisateur de charger JavaScript que cette page particulière ne nécessite pas.
Marnen Laibow-Koser
4
@ MarnenLaibow-Koser la raison de ne pas ajouter de balises de script manuelles à chaque page unique est que vous devez télécharger ce contenu de script sur chaque page vue. Si vous pouvez empaqueter tout le javascript dans application.js en utilisant le pipeline d'actifs, l'utilisateur télécharge ces scripts une seule fois et extrait application.js du cache lors de tous les chargements de pages suivants
jakeonrails
@jakeonrails "la raison de ne pas ajouter de balises de script manuelles à chaque page unique est que vous devez télécharger ce contenu de script sur chaque page vue" - tout à fait faux. Le script sera téléchargé une fois, puis récupéré dans le cache du navigateur sur d'autres demandes. «Si vous êtes en mesure de regrouper tout le javascript dans application.js à l'aide du pipeline d'actifs, l'utilisateur télécharge ces scripts une seule fois» - vrai, mais au prix de beaucoup de code inutile. Si vous pouvez structurer votre JS en plusieurs petits fichiers au lieu d'un seul gros, vous bénéficiez d'avantages de mise en cache sans code inutile.
Marnen Laibow-Koser
1
@ MarnenLaibow-Koser Je pense qu'il aurait été préférable de dire que si vous regroupez tout dans un seul script, votre utilisateur n'a qu'à télécharger 1 script pour n'importe quelle page de votre site. Si vous avez plusieurs scripts pour différentes parties de votre application, il est évident que l'utilisateur doit télécharger plus d'un script. Les deux méthodes seront bien sûr mises en cache, mais dans la plupart des cas (petites et moyennes applications), servir une seule application.js une fois sera plus efficace pour le téléchargement. L'analyse du JS peut être une autre histoire, selon ce que vous servez.
jakeonrails
1
@Ziggy De plus, si le petit fichier n'est utilisé que sur 8 pages sur 100, pourquoi le code devrait-il toujours rester dans le cache de l'utilisateur? Mieux vaut vraiment laisser tomber ce qui n'est pas nécessaire.
Marnen Laibow-Koser
16

Je me rends compte que je viens à cette fête un peu en retard, mais je voulais apporter une solution que j'ai utilisée récemment. Cependant, permettez-moi d'abord de mentionner ...

The Rails 3.1 / 3.2 Way (Non, monsieur. Je n'aime pas ça.)

Voir: http://guides.rubyonrails.org/asset_pipeline.html#how-to-use-the-asset-pipeline

J'inclus les éléments suivants par souci d'exhaustivité dans cette réponse, et parce que ce n'est pas une solution non viable ... bien que je ne m'en soucie pas beaucoup.

Le "Rails Way" est une solution orientée contrôleur, plutôt que d'être orientée vue comme l'auteur original de cette question l'a demandé. Il existe des fichiers JS spécifiques au contrôleur qui portent le nom de leurs contrôleurs respectifs. Tous ces fichiers sont placés dans une arborescence de dossiers qui n'est PAS incluse par défaut dans aucune des applications.js nécessitent des directives.

Pour inclure du code spécifique au contrôleur, les éléments suivants sont ajoutés à une vue.

<%= javascript_include_tag params[:controller] %>

Je déteste cette solution, mais elle est là et c'est rapide. Vraisemblablement, vous pouvez à la place appeler ces fichiers quelque chose comme "people-index.js" et "people-show.js", puis utiliser quelque chose comme "#{params[:controller]}-index"pour obtenir une solution orientée vue. Encore une fois, solution rapide, mais cela ne me convient pas.

Ma manière d'attribut de données

Appelez-moi fou, mais je veux que TOUS mes JS soient compilés et minifiés dans application.js lorsque je déploie. Je ne veux pas avoir à me rappeler d'inclure ces petits fichiers de retardateur partout.

Je charge tout mon JS dans un fichier compact, bientôt mis en cache par le navigateur. Si un certain morceau de mon application.js doit être lancé sur une page, je laisse le HTML me le dire, pas Rails.

Plutôt que de verrouiller mon JS sur des ID d'élément spécifiques ou de joncher mon HTML avec des classes de marqueurs, j'utilise un attribut de données personnalisé appelé data-jstags.

<input name="search" data-jstag="auto-suggest hint" />

Sur chaque page, j'utilise - insérez ici la méthode de bibliothèque JS préférée - pour exécuter le code lorsque le DOM a terminé le chargement. Ce code d'amorçage effectue les actions suivantes:

  1. Itérer sur tous les éléments du DOM marqués d'un data-jstag
  2. Pour chaque élément, divisez la valeur d'attribut sur l'espace, créant un tableau de chaînes de balises.
  3. Pour chaque chaîne de balise, effectuez une recherche dans un hachage pour cette balise.
  4. Si une clé correspondante est trouvée, exécutez la fonction qui lui est associée, en passant l'élément en tant que paramètre.

Disons que j'ai défini ce qui suit quelque part dans mon application.js:

function my_autosuggest_init(element) {
  /* Add events to watch input and make suggestions... */
}

function my_hint_init(element) {
  /* Add events to show a hint on change/blur when blank... */
  /* Yes, I know HTML 5 can do this natively with attributes. */
}

var JSTags = {
  'auto-suggest': my_autosuggest_init,
  'hint': my_hint_init
};

L'événement d'amorçage va appliquer les fonctions my_autosuggest_initet my_hint_inità l'entrée de recherche, la transformant en une entrée qui affiche une liste de suggestions pendant que l'utilisateur tape, ainsi que fournissant une sorte d'indice d'entrée lorsque l'entrée est laissée vide et non focalisée.

À moins qu'un élément ne soit marqué data-jstag="auto-suggest", le code de suggestion automatique ne se déclenche jamais. Cependant, il est toujours là, minifié et finalement mis en cache dans mon application.js pour les moments où j'en ai besoin sur une page.

Si vous devez transmettre des paramètres supplémentaires à vos fonctions JS balisées, vous devrez faire preuve de créativité. Soit ajouter des attributs de paramètres de données, trouver une sorte de syntaxe de paramètre, ou même utiliser une approche hybride.

Même si j'ai un flux de travail compliqué qui semble spécifique au contrôleur, je vais juste créer un fichier pour celui-ci dans mon dossier lib, le mettre dans application.js et le marquer avec quelque chose comme 'new-thing-wizard'. Lorsque mon bootstrap atteint cette balise, mon gentil assistant sophistiqué sera instancié et exécuté. Il s'exécute pour les vues de ce contrôleur en cas de besoin, mais n'est pas autrement couplé au contrôleur. En fait, si je code correctement mon assistant, je pourrais être en mesure de fournir toutes les données de configuration dans les vues et donc être en mesure de réutiliser mon assistant plus tard pour tout autre contrôleur qui en a besoin.

Quoi qu'il en soit, c'est ainsi que j'implémente JS spécifique à une page depuis un certain temps maintenant, et cela m'a bien servi à la fois pour la conception de sites simples et pour des applications plus complexes / riches. J'espère que l'une des deux solutions que j'ai présentées ici, à ma façon ou à la manière de Rails, sera utile à quiconque rencontrera cette question à l'avenir.

Ryan
la source
6
Un petit détail: il y a cette notion dans votre réponse qu'une fois que le js est mis en cache par le navigateur, il n'a aucun impact. Ce n'est pas tout à fait vrai. Le navigateur évite en effet le téléchargement, si le fichier js est correctement mis en cache, mais il compile toujours le code à chaque rendu de page. Vous devez donc équilibrer les compromis. Si vous avez un grand nombre de JS au total, mais que seulement une partie est utilisée par page, vous pourrez peut-être améliorer les temps de page en séparant le JS.
sujal
Pour en savoir plus sur les effets pratiques de cette étape de compilation dont je parle, voir l'explication de 37 Signals sur l'impact de pjax sur Basecamp Suivant: 37signals.com/svn/posts/…
sujal
C'est un bon point. Après avoir lu l'article et regardé en arrière sur les projets où j'ai utilisé la solution ci-dessus, je me rends compte que j'ai écrit essentiellement la même solution "envoyer le HTML modifié" qu'ils mentionnent dans l'article. La recompilation fréquente de JS n'avait pas été un problème dans mes projets à cause de cela. L'étape de compilation est quelque chose que je garderai à l'esprit lorsque je travaille sur des sites moins orientés "applications de bureau".
Ryan
2
Downvoting for "Call me crazy, but I want ALL of my JS compiled and minified into application.js when I deploy." Vous ne voulez vraiment pas cela, car cela oblige l'utilisateur à charger du JavaScript dont il n'a pas besoin et cela fait que vos gestionnaires recherchent des attributs qui ne seront même pas là. Tout avoir dans app.js est tentant, et Rails le rend certainement facile, mais la bonne chose à faire est de mieux modulariser JavaScript.
Marnen Laibow-Koser
Vous avez droit à une opinion différente ... et techniquement, vous pouvez voter contre une différence d'opinion. Cependant, il serait bien de voir une justification pour expliquer pourquoi un fichier volumineux et mis en cache est inférieur à forcer plusieurs requêtes HTTP à saisir JS modularisé. En outre, vous vous trompez en ce qui concerne la procédure de recherche de gestionnaire. Les valeurs des balises ne sont PAS recherchées. Une seule recherche est effectuée et elle extrait tous les éléments qui ont un attribut data-jstag. Il ne recherche pas par nom de balise, il trouve simplement tous les éléments qui ont des balises, puis instancie uniquement les objets nécessaires.
Ryan
7

Cela a été répondu et accepté il y a longtemps, mais j'ai trouvé ma propre solution basée sur certaines de ces réponses et mon expérience avec Rails 3+.

Le portefeuille d'actifs est doux. Utilise le.

Tout d'abord, dans votre application.jsfichier, supprimez//= require_tree.

Ensuite, dans votre application_controller.rbméthode de création d'une aide:

helper_method :javascript_include_view_js //Or something similar

def javascript_include_view_js
    if FileTest.exists? "app/assets/javascripts/"+params[:controller]+"/"+params[:action]+".js.erb"
        return '<script src="/assets/'+params[:controller]+'/'+params[:action]+'.js.erb" type="text/javascript"></script>'
    end
end

Ensuite, dans votre application.html.erbfichier de mise en page, ajoutez votre nouvel assistant parmi les inclus javascript existants, préfixés avec l' rawaide:

<head>
    <title>Your Application</title>
    <%= stylesheet_link_tag "application", :media => "all" %>
    <%= javascript_include_tag "application" %>
    <%= raw javascript_include_view_js %>
</head>

Voila, maintenant vous pouvez facilement créer du javascript spécifique à la vue en utilisant la même structure de fichiers que vous utilisez partout ailleurs dans les rails. Collez simplement vos fichiers app/assets/:namespace/:controller/action.js.erb!

J'espère que cela aide quelqu'un d'autre!

Mike A
la source
1
Cela ne causera-t-il pas des problèmes une fois les actifs précompilés et au moment de l'exécution, le <%= raw ... %>retourne un 404?
Nishant
Je pense que le pipeline d'actifs n'est pas agréable, car il crée un tas de fichiers qui ne devraient souvent pas être utilisés. Donc, pour moi, compter sur le pipeline d'actifs crée une dépendance à l'égard d'un système inefficace.
Deborah
1
@DeborahSpeece Quand le pipeline d'actifs crée-t-il des fichiers qui ne devraient pas être utilisés? Confondez-vous le pipeline d'actifs (bon) avec require_tree /(mauvais)?
Marnen Laibow-Koser
6

Vous pouvez ajouter cette ligne dans votre fichier de mise en page (par exemple application.html.erb) pour charger automatiquement le fichier javascript spécifique au contrôleur (celui qui a été créé lorsque vous avez généré le contrôleur):

<%= javascript_include_tag params[:controller] %>

Vous pouvez également ajouter une ligne pour charger automatiquement un fichier de script par action.

<%= javascript_include_tag params[:controller] + "/" + params[:action] %>

Mettez simplement vos scripts de page dans une sous-répertoire nommée d'après le nom du contrôleur. Dans ces fichiers, vous pouvez inclure d'autres scripts en utilisant = require. Il serait intéressant de créer un assistant pour inclure le fichier uniquement s'il existe, afin d'éviter un échec 404 dans le navigateur.

mcubik
la source
6
<%= javascript_include_tag params[:controller] %>
Monsieur Bohr
la source
2
On dirait qu'il pourrait être en mesure de répondre à la question. Pourriez-vous s'il vous plaît ajouter plus à la réponse pour l'étoffer?
6

Vous trouverez peut-être la gemme pluggable_js comme solution appropriée.

peresleguine
la source
5

La gemme LoadJS est une autre option:

LoadJS fournit un moyen de charger du code Javascript spécifique à une page dans une application Rails sans perdre la magie fournie par Sprockets. Tout votre code Javascript continuera d'être minifié dans un fichier Javascript mais certaines parties de celui-ci ne seront exécutées que pour certaines pages.

https://github.com/guidomb/loadjs

meleyal
la source
3

La réponse de Philip est assez bonne. Voici le code pour le faire fonctionner:

Dans application.html.erb:

<body class="<%=params[:controller].parameterize%>">

En supposant que votre contrôleur s'appelle Projets, cela générera:

<body class="projects">

Puis dans projects.js.coffee:

jQuery ->
  if $('body.projects').length > 0  
     $('h1').click ->
       alert 'you clicked on an h1 in Projects'
Rahil Sondhi
la source
Downvoting: toute solution qui met une classe sur l' <body>est ipso facto incorrect. Voir mes commentaires ailleurs sur cette page.
Marnen Laibow-Koser
Ne fais pas ça. Le problème ici est que chaque fois que vous en ajoutez un, vous ajoutez un autre morceau de js qui doit être exécuté au chargement de la page. Cela pourrait certainement entraîner une dégradation des performances à mesure que votre projet se développe.
ifightcrime
2

Les scripts Java ne sont fusionnés que lorsque vous demandez à Rails (Sprockets, plutôt) de les fusionner.

yfeldblum
la source
Bien sûr. Je suppose que je demande parce que les valeurs par défaut de Rails incluent tout dans le dossier ... ce qui signifie que David a l'intention que vous fassiez cela. Mais comme je l'ai dit dans l'autre commentaire à @rubyprince, je ne suis pas sûr de l'exécution quand cela se fait de cette façon. Je pense que je dois désactiver //= require_tree .?
Fire Emblem
@FireEmblem Oui. require_tree .est généralement une mauvaise idée.
Marnen Laibow-Koser
2

Voici comment j'ai résolu le problème de style: (excusez le Haml)

%div{:id => "#{params[:controller].parameterize} #{params[:view]}"}
    = yield

De cette façon, je démarre tous les fichiers .css.sass spécifiques à la page avec:

#post
  /* Controller specific code here */
  &#index
    /* View specific code here */
  &#new
  &#edit
  &#show

De cette façon, vous pouvez facilement éviter tout affrontement. En ce qui concerne les fichiers .js.coffee , vous pouvez simplement initialiser des éléments comme;

$('#post > #edit') ->
  $('form > h1').css('float', 'right')

J'espère que cela a aidé certains.

Zeeraw
la source
1
Relisez le dernier bit s'il vous plaît, pour javascript, vous pouvez profiter de la même structure utilisée pour les feuilles de style pour initialiser les fonctions spécifiques à la vue.
Zeeraw
Philip, $('#post > #edit') ->semble invalide. Comment définissez-vous jQuery pour travailler dans une étendue?
Ramon Tayag
2
Récemment, j'ai commencé à charger tous les scripts Java et les feuilles de style spécifiques au contrôleur en appelant ceci dans le fichier application.html.haml; = javascript_include_tag "application"et de = javascript_include_tag params[:controller]cette façon, je peux garder le code javascript confiné sans avoir à spécifier une portée à l'intérieur du fichier.
2011
2

Je suis d'accord avec votre réponse, pour vérifier si ce sélecteur est là, utilisez:

if ($(selector).length) {
    // Put the function that does not need to be executed every page
}

(personne n'a vu ajouter la solution réelle)

Kyle C
la source
2

Je ne vois pas de réponse qui rassemble vraiment tout cela et vous l'explique. Ainsi, je vais essayer de mettre meleyal , sujal (à la ClosureCowboy ), la première partie de la réponse de Ryan , et même la déclaration audacieuse de Gal à propos de Backbone.js ... tous ensemble d'une manière qui est courte et claire. Et, qui sait, je pourrais même répondre aux exigences de Marnen Laibow-Koser .

Exemples de modifications

assets / javascripts / application.js

//= require jquery
//= require jquery_ujs
//= require lodash.underscore.min
...


views / layouts / application.html.erb

  ...
  </footer>

  <!-- Javascripts ================================================== -->
  <!-- Placed at the end of the document so the pages load faster -->
  <%= javascript_include_tag "application" %>
  <%= yield :javascript %>

</body>
</html>


views / foo / index.html.erb

...
<% content_for :javascript do %>
  <%= javascript_include_tag params[:controller] %>
<% end %>


assets / javascripts / foo.js

//= require moment
//= require_tree ./foostuff


actifs / javascripts / foostuff / foothis.js.coffee

alert "Hello world!"


Brève description

  • Supprimez //= require_tree .de application.js et répertoriez uniquement le JS que chaque page partage.

  • Les deux lignes indiquées ci-dessus dans application.html.erb indiquent à la page où inclure application.js et votre JS spécifique à la page.

  • Les trois lignes montrées ci-dessus dans index.html.erb indiquent à votre vue de rechercher un JS spécifique à une page et de l'inclure dans une région de rendement nommée appelée ": javascript" (ou tout ce que vous voulez lui donner). Dans cet exemple, le contrôleur est "foo", donc Rails tentera d'inclure "foo.js" dans la région de rendement: javascript dans la présentation de l'application.

  • Listez votre JS spécifique à une page dans foo.js (ou quel que soit le nom du contrôleur). Liste des bibliothèques communes, un arbre, des répertoires, etc.

  • Gardez votre JS spécifique à une page personnalisée quelque part où vous pouvez facilement le référencer en dehors de votre autre JS personnalisé. Dans cet exemple, foo.js nécessite l'arborescence foostuff, placez-y votre JS personnalisé, comme foothis.js.coffee .

  • Il n'y a pas de règles strictes ici. N'hésitez pas à déplacer les choses et peut-être même à créer plusieurs régions de rendement de différents noms dans différentes dispositions si nécessaire. Cela montre juste un premier pas en avant possible. (Je ne le fais pas exactement comme cela étant donné notre utilisation de Backbone.js. Je pourrais aussi choisir de déposer foo.js dans un dossier appelé foo au lieu de foostuff mais je n'ai pas encore décidé cela.)

Remarques

Vous pouvez faire des choses similaires avec CSS et <%= stylesheet_link_tag params[:controller] %>cela dépasse la portée de la question.

Si j'ai raté une meilleure pratique flagrante ici, envoyez-moi une note et je vais m'adapter. Rails est assez nouveau pour moi et, honnêtement, je ne suis pas très impressionné jusqu'à présent par le chaos qu'il apporte par défaut au développement de l'entreprise et tout le trafic généré par le programme Rails moyen.

juanitogan
la source
cela ressemble à la voie à suivre, je vais voir si je peux l'implémenter dans ma propre application, merci pour la réponse détaillée.
martincarlin87
1

J'ai une autre solution qui, bien que primitive, fonctionne bien pour moi et ne nécessite aucune stratégie de chargement sélective sophistiquée. Mettez votre fonction prête pour le document nornal, mais testez ensuite l'emplacement actuel de Windows pour voir si c'est la page à laquelle votre javascript est destiné:

$(document).ready(function() {
   if(window.location.pathname.indexOf('/yourpage') != -1) {
          // the javascript you want to execute
   }
}

Cela permet toujours à tous les js d'être chargés par rails 3.x dans un petit paquet, mais ne génère pas beaucoup de surcharge ou de conflits avec les pages auxquelles le js n'est pas destiné.

Peter Abramowitsch
la source
1

La réponse de ryguy est une bonne réponse, même si elle a été déclassée en points négatifs.

Surtout si vous utilisez quelque chose comme Backbone JS - chaque page a sa propre vue Backbone. Ensuite, le fichier erb n'a qu'une seule ligne de javascript en ligne qui déclenche la bonne classe d'affichage de la dorsale. Je le considère comme une seule ligne de «code de colle» et donc le fait que son inline est OK. L'avantage est que vous pouvez conserver votre "require_tree" qui permet au navigateur de mettre en cache tout le javascript.

dans show.html.erb, vous aurez quelque chose comme:

<% provide :javascript do %>
  <%= javascript_include_tag do %>
    (new app.views.ProjectsView({el: 'body'})).render();
  <% end %>
<% end do %>

et dans votre fichier de mise en page, vous aurez besoin de:

<%= yield :javascript %>
Fille
la source
Downvoting. JavaScript en ligne n'est jamais une bonne idée. Même s'il s'agit d'un code de collage, il doit se trouver dans un fichier externe.
Marnen Laibow-Koser
1

Déplacez tous vos fichiers JS commom vers un sous-dossier comme 'app / assets / javascript / global' puis dans application.js, modifiez la //= require_tree .ligne en //= require_tree ./global.

Maintenant, vous êtes libre de placer votre JS spécifique au contrôleur à la racine 'app / assets / javascript /' et ils ne seront pas inclus dans le JS compilé, utilisé uniquement lorsque vous les appelez via = javascript_include_tagsur votre contrôleur / vue.

Gedean Dias
la source
Pas question, c'est un crapload de JavaScript à charger pour une page. Peu importe même s'il est mis en cache.
jackyalcine
1

Bien que vous ayez plusieurs réponses ici, je pense que votre montage est probablement le meilleur pari. Un modèle de conception que nous utilisons dans notre équipe que nous avons obtenu de Gitlab est le modèle Dispatcher. Il fait quelque chose de similaire à ce dont vous parlez, mais le nom de la page est défini dans la balise body par rails. Par exemple, dans votre fichier de mise en page, incluez simplement quelque chose comme (dans HAML):

%body{'data-page' => "#{controller}:#{action}" }

N'ayez alors qu'une seule fermeture et une instruction switch dans votre dispatcher.js.coffeefichier dans votre dossier javascripts comme ceci:

$ ->
  new Dispatcher()

class Dispatcher
  constructor: ->
    page = $('body').attr('data-page')
    switch page
      when 'products:index'
        new Products() 
      when 'users:login'
        new Login()

Tout ce que vous devez faire dans les fichiers individuels ( par exemple products.js.coffeeou login.js.coffeepar exemple) est de les enfermer dans une classe, puis de globaliser ce symbole de classe afin que vous puissiez y accéder dans le répartiteur:

class Products
  constructor: ->
    #do stuff
@Products = Products

Gitlab en a plusieurs exemples que vous voudrez peut-être fouiller au cas où vous seriez curieux :)

onetwopunch
la source
1

Le projet Paloma offre une approche intéressante pour gérer le code javascript spécifique à une page.

Exemple d'utilisation de leurs documents:

var UsersController = Paloma.controller('Users');

// Executes when Rails User#new is executed.
UsersController.prototype.new = function(){
   alert('Hello Sexy User!' );
};
zavg
la source
1

Étape 1. supprimer require_tree. dans votre application.js et application.css.

Étape 2. Modifiez votre application.html.erb (par défaut par rails) dans le dossier de disposition. Ajoutez "params [: contrôleur]" dans les balises suivantes.

<%= stylesheet_link_tag    'application', params[:controller], media: 'all', 'data-turbolinks-track' => true %>

<%= javascript_include_tag 'application', params[:controller], 'data-turbolinks-track' => true %>

Étape 3. Ajoutez un fichier dans config / initializers / assets.rb

%w( controller_one controller_two controller_three ).each do |controller|
  Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.js.coffee", "#{controller}.css", "#{controller}.scss"]
end

références: http://theflyingdeveloper.com/controller-specific-assets-with-rails-4/

etlds
la source
Bien que cela puisse théoriquement répondre à la question, il serait préférable d'inclure ici les parties essentielles de la réponse et de fournir le lien de référence.
Bhargav Rao
0

Je n'ai pas essayé cela, mais il semble que ce qui suit est vrai:

  • si vous avez un content_for qui est javascript (par exemple avec du vrai javascript en son sein), les sprockets ne le sauraient pas et donc cela fonctionnerait de la même manière que maintenant.

  • si vous voulez exclure un fichier du grand paquet de javascript, vous irez dans le fichier config / sprockets.yml et modifierez les fichiers source en conséquence. Ensuite, vous devez simplement inclure les fichiers que vous avez exclus si nécessaire.

Bill Eisenhauer
la source
Est-ce qu'exclure des fichiers ou utiliser du javascript personnalisé sur la page elle-même est "la bonne façon"? Est-ce ainsi que David voulait que les gens l'utilisent?
Fire Emblem
@FireEmblem Peu m'importe ce que voulait David, car je ne pense pas que David comprenne comment organiser JavaScript correctement.
Marnen Laibow-Koser
0

J'ai combiné quelques réponses en:

Aide à l'application:

module ApplicationHelper
  def js_page_specific_include
    page_specific_js = params[:controller] + '_' + params[:action]
    if Rails.application.assets.find_asset(page_specific_js).nil?
      javascript_include_tag 'application', 'data-turbolinks-track' => true
    else
      javascript_include_tag 'application', page_specific_js, 'data-turbolinks-track' => true
    end
  end
end

layouts / application.html.haml:

 <!DOCTYPE html>
%html{lang: 'uk'}
  %head   
    = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
   bla-bla-bla
    = js_page_specific_include   
   bla-bla-bla  
kapellan
la source
0

Premièrement: supprimer \\=require_treede application.js Deuxièmement: tout votre code JS doit être attribué à /app/assets/javascritptet tout votre code CSS doit être localisé à/app/assets/stylesheets

Nicollas
la source
-2

Suivant l'exemple de Ryan, voici ce que j'ai fait-

application.js.coffee

$ ->
    view_method_name = $("body").data("view") + "_onload"
    eval("#{view_method_name}()") if eval("typeof #{view_method_name} == 'function'")
    view_action_method_name = $("body").data("view") + "_"+$("body").data("action")+"_onload"
    eval("#{view_action_method_name}()") if eval("typeof #{view_action_method_name} == 'function'")

users.js.coffee (coffeescript spécifique au contrôleur, par exemple contrôleur: utilisateurs, action: tableau de bord)

window.users_dashboard_onload = () ->
    alert("controller action called")
window.users_onload = () ->
    alert("controller called")

application.html.haml

%body{:data=>{:view=>controller.controller_name, :action=>controller.action_name}}
Kruttik
la source
Downvoting. C'est ridiculement alambiqué - sans parler de l'insécurité (en raison de eval) si votre HTML est compromis par un serveur piraté ou un script utilisateur malveillant.
Marnen Laibow-Koser
-3

Voici comment le faire, surtout si vous n'avez pas à exécuter des tonnes de bibliothèques pour votre page spécifique, mais uniquement pour exécuter plus ou moins quelques centaines de lignes de JS.

Puisqu'il est parfaitement possible d'incorporer du code Javascript dans HTML, créez simplement sous le répertoire app / views shared.js et placez-y votre code spécifique page / pages dans my_cool_partial.html.erb

<script type="text/javascript"> 
<!--
  var your_code_goes_here = 0;
  function etc() {
     ...
  }
-->
</script>

Alors maintenant, où que vous vouliez, vous faites simplement:

  = render :partial => 'shared.js/my_cool_partial'

Et c'est tout, k?

valk
la source
2
Downvoting. Le JavaScript en ligne n'est jamais recommandé. HTML ne doit contenir que du balisage. JS et CSS doivent être dans des fichiers distincts et réutilisables.
Marnen Laibow-Koser