Wo platzieren Sie mit Rails 3.1 Ihren "seitenspezifischen" JavaScript-Code?

388

Nach meinem Verständnis wird Ihr gesamtes JavaScript in einer Datei zusammengeführt. Rails tut dies standardmäßig, wenn es //= require_tree .am Ende Ihrer application.jsManifestdatei hinzugefügt wird.

Das klingt nach einem echten Lebensretter, aber ich bin ein wenig besorgt über seitenspezifischen JavaScript-Code. Wird dieser Code auf jeder Seite ausgeführt? Das Letzte, was ich möchte, ist, dass alle meine Objekte für jede Seite instanziiert werden, wenn sie nur auf einer Seite benötigt werden.

Gibt es nicht auch Potenzial für Code, der ebenfalls zusammenstößt?

Oder setzen Sie ein kleines scriptTag am Ende der Seite, das nur eine Methode aufruft, die den Javascript-Code für die Seite ausführt?

Benötigen Sie dann nicht mehr require.js?

Vielen Dank

EDIT : Ich schätze alle Antworten ... und ich glaube nicht, dass sie wirklich auf das Problem kommen. Einige von ihnen handeln vom Styling und scheinen sich nicht darauf zu beziehen ... und andere erwähnen nur javascript_include_tag... was ich weiß (offensichtlich ...), aber es scheint, dass der Weg von Rails 3.1 in die Zukunft darin besteht, alles zusammenzufassen Ihr JavaScript in 1 Datei, anstatt einzelnes JavaScript am Ende jeder Seite zu laden.

Die beste Lösung, die ich finden kann, besteht darin, bestimmte Funktionen in divTags mit ids oder classes zu verpacken . Im JavaScript-Code überprüfen Sie einfach, ob sich das idoder classauf der Seite befindet, und wenn dies der Fall ist, führen Sie den zugehörigen JavaScript-Code aus. Auf diese Weise wird der JavaScript-Code nicht ausgeführt, wenn sich das dynamische Element nicht auf der Seite befindet - obwohl er in der application.jsvon Sprockets gepackten umfangreichen Datei enthalten ist.

Meine obige Lösung hat den Vorteil, dass ein Suchfeld, das auf 8 der 100 Seiten enthalten ist, nur auf diesen 8 Seiten ausgeführt wird. Sie müssen auch nicht den gleichen Code auf 8 Seiten der Site einfügen. Tatsächlich müssen Sie nie wieder irgendwo manuelle Skript-Tags auf Ihrer Website einfügen.

Ich denke, das ist die eigentliche Antwort auf meine Frage.

Feuerzeichen
quelle
11
"Der Weg für Rails 3.1 besteht darin, Ihr gesamtes Javascript in eine Datei zu packen, anstatt einzelnes Javascript am Ende jeder Seite zu laden." - Nur weil das Rails-Kernteam wirklich schlecht weiß und war, wie JavaScript verwalten. Kleine Dateien sind im Allgemeinen besser (siehe meine Kommentare an anderer Stelle). Wenn es um JavaScript geht, ist der Rails-Weg selten der richtige (mit Ausnahme der Asset-Pipeline, die den Arsch tritt, und der Förderung von CoffeeScript).
Marnen Laibow-Koser
Sie werden also Ihre seitenspezifischen JS-Dateien auf jeder Seite einfügen? Ich denke, das ist eine Verschwendung, ich stimme der Antwort von ClosureCowboy mehr zu.
Gerky
1
Haben Sie sich die akzeptierte Antwort auf diese Frage angesehen? stackoverflow.com/questions/6571753/…
rassom
1
@DutGRIFF Mit anderen Worten: Nein, in diesem Fall ist es nicht am besten, die Dinge auf Rails-Weise zu erledigen (oder zumindest nicht alles einzutragen application.js), und tatsächlich zeigt die von Ihnen angegebene Referenz, warum dies so ist: Herunterladen ist das langsamster Teil des JS-Ausführungsprozesses. Viele kleine Dateien können zwischengespeichert werden als eine große. Die Leute von Unholy Rails scheinen also nicht zu erkennen, dass ihre Empfehlungen nicht mit den Prinzipien übereinstimmen, an die sie sich halten wollen, und daher sollten ihre Empfehlungen nicht ernst genommen werden.
Marnen Laibow-Koser
1
@DutGRIFF Nein, eine große JS-Datei ist normalerweise auch nach dem Zwischenspeichern keine gute Sache. Siehe meine Kommentare an anderer Stelle auf dieser Seite: Kleine Dateien können besser auf bestimmte Seiten abzielen und mit einer feineren Granularität zwischengespeichert werden. Ich sehe keinen guten Anwendungsfall für eine einzige große Datei , es sei denn es keine seitenspezifische Code ist überhaupt .
Marnen Laibow-Koser

Antworten:

157

In den Asset Pipeline-Dokumenten wird vorgeschlagen, wie Controller-spezifisches JS ausgeführt wird:

Wenn beispielsweise a ProjectsControllergeneriert wird, wird unter eine neue Datei angezeigtapp/assets/javascripts/projects.js.coffee und eine weitere bei app/assets/stylesheets/projects.css.scss. Sie sollten JavaScript oder CSS, die für einen Controller eindeutig sind, in die jeweiligen Asset-Dateien einfügen, da diese Dateien dann nur für diese Controller mit Zeilen wie <%= javascript_include_tag params[:controller] %>oder geladen werden können <%= stylesheet_link_tag params[:controller] %>.

Link zu: asset_pipeline

meleyal
quelle
50
Dies ist der eleganteste Weg, dies zu tun. Sie müssen jedoch auch die Zeile // = require_tree entfernen. aus der application.js.coffee
zsljulius
2
Ich stimme dieser Methode voll und ganz zu. Die anderen Methoden scheinen sehr klobig zu sein und laden immer noch eine riesige js-Datei. Das Projekt, an dem ich arbeite, hat JS-Dateien / Plugins usw. im Wert von fast 2 MB, nachdem sie kombiniert / minimiert wurden.
Bill Garrison
2
Ich bin ziemlich neu bei Rails, aber es scheint mir, dass dies das Standardverhalten sein sollte.
Ross Hambrick
12
Für die aktionsspezifische Steuerung habe ich dies in meinem Layout, da nicht jede Aktion für jeden Controller eine bestimmte JS hat. page_specific_js = "#{params[:controller]}_#{params[:action]}"und dann; javascript_include_tag page_specific_js if Rails.application.assets.find_asset page_specific_js
Sujimichi
2
Werden die Controller-spezifischen Aktionen immer noch minimiert? Werden sie der einzelnen js-Datei hinzugefügt, die von Kettenrädern erstellt wird, oder führt dies zu mehreren Anfragen nach Asset-Dateien?
Jason
77

Für die seitenspezifischen js können Sie die Garber-Irish-Lösung verwenden .

Ihr Rails-Javascripts-Ordner könnte also für zwei Controller so aussehen - Autos und Benutzer:

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

Und Javascripts werden so aussehen:

// 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
}

und markup_based_js_execution enthalten Code für das UTIL-Objekt und für die Ausführung von UTIL.init, das für DOM bereit ist.

Und vergessen Sie nicht, dies in Ihre Layoutdatei aufzunehmen:

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

Ich denke auch, dass es besser ist, Klassen anstelle von data-*Attributen zu verwenden, um das seitenspezifische CSS zu verbessern. Wie Jason Garber bereits erwähnt hat: Seitenspezifische CSS-Selektoren können sehr umständlich werden (wenn Sie data-*Attribute verwenden).

Ich hoffe, dies wird dir helfen.

welldan97
quelle
4
Was ist, wenn Sie eine Variable benötigen, die für alle Aktionen im Controller des Benutzers verfügbar ist, in anderen Controllern jedoch nicht? Hat diese Methode nicht einige Probleme mit dem Umfang?
tybro0103
@ tybro0103, ich denke, um dieses Verhalten zu implementieren, möchten Sie etwas wie window.varForOneController='val'in dieser Controller-Init-Funktion schreiben . Auch Gon Gem kann hier helfen ( github.com/gazay/gon ). Es kann andere Problemumgehungen geben.
welldan97
1
@ welldan97 Downvoting nicht für Ihre Erklärung - was ausgezeichnet ist - sondern weil die Garber-Irish-Struktur böse ist. Es lädt alle Ihre JS auf jeder Seite und hängt von Klassen und IDs des <body> -Elements ab, um die Dinge zu sortieren. Das ist ein sicheres Zeichen für den Kampf gegen das DOM: Unter normalen Umständen sollte das <body> -Element keine Klasse oder ID benötigen, da es in einem Dokument immer nur eine gibt. Der richtige Weg, dies zu tun, besteht einfach darin, das //= require_tree .seitenspezifische JavaScript zu entfernen und zu verwenden. Wenn Sie aktiv versuchen, dies nicht zu tun, streben Sie nach schlechter Praxis.
Marnen Laibow-Koser
2
@ MarnenLaibow-Koser Persönlich glaube ich, dass das Laden aller js auf jeder Seite für die meisten Projekte gut ist, wenn Sie alle js in einer Datei kombinieren und minimieren. Ich glaube, es funktioniert insgesamt schneller für den Benutzer. Zumindest ist es eher ein Konflikt zwischen einer js-Datei und vielen anderen (siehe stackoverflow.com/questions/555696/… ). Es ist auch nichts Schlechtes, Klassen und IDs für den Textkörper zu verwenden, wenn dies den Code vereinfacht und für Sie funktioniert. Modernizr ( modernizr.com ) macht dies und einige andere Bibliotheken auch.
Welldan97
2
@ MarnenLaibow-Koser Die Rails-Asset-Pipeline scheint mir ein guter Kandidat für den Vergleich mit der Zusammenstellung zu sein. Ein Programmierer schreibt sein Javascript in schönen entkoppelten Modulen, und dann wird es zusammengefasst, minimiert und bereitgestellt. Genau wie bei kompilierten Sprachen wird es immer Programmierer geben, die glauben, dem Compiler einen Schritt voraus zu sein ... aber ich denke, das ist selten wahr.
Ziggy
65

Ich sehe, dass Sie Ihre eigene Frage beantwortet haben, aber hier ist eine andere Option:

Grundsätzlich gehen Sie davon aus, dass

//= require_tree .

ist erforderlich. Es ist nicht. Fühlen Sie sich frei, es zu entfernen. In meiner aktuellen Anwendung, die ich zum ersten Mal ehrlich mit 3.1.x mache, habe ich drei verschiedene JS-Dateien der obersten Ebene erstellt. Meine application.jsDatei hat nur

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

Auf diese Weise kann ich Unterverzeichnisse mit eigenen JS-Dateien der obersten Ebene erstellen, die nur das enthalten, was ich benötige.

Die Schlüssel sind:

  1. Sie können entfernen require_tree - Mit Rails können Sie die getroffenen Annahmen ändern
  2. Der Name hat nichts Besonderes application.js- jede Datei im assets/javascriptUnterverzeichnis kann Preprozessor-Direktiven mit enthalten//=

Ich hoffe, das hilft und fügt der Antwort von ClosureCowboy einige Details hinzu.

Sujal

Sujal
quelle
8
+1 Das ist großartig für einen Neuling wie mich. Ich würde es +2 geben, wenn ich könnte.
jrhorn424
5
@ Sujal Genau. Das Rails-Kernteam ist bekannt für seine miserable JavaScript-Verwaltung. Sie können ihre Vorschläge ignorieren und einfach die guten Teile der Asset-Pipeline verwenden. :)
Marnen Laibow-Koser
1
Vielen Dank für diesen Rat. Ich habe nicht mehrere JS-Dateien der obersten Ebene, abhängig vom Modul meiner App. Funktioniert gut.
Elsurudo
1
+1 Der wichtige Punkt hier ist für mich , dass Sie ersetzen //= require_tree .mit , //= require_directory .so dass Sie alle vorhandenen Dateien halten , wo sie sind , und erstellen Sie neue Verzeichnisse für seitenspezifische Dateien.
Zelanix
41

Eine weitere Option: Um seiten- oder modellspezifische Dateien zu erstellen, können Sie Verzeichnisse in Ihrem assets/javascripts/Ordner erstellen .

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

Ihre Hauptmanifestdatei application.jskann so konfiguriert werden, dass ihre Dateien geladen werden global/. Bestimmte Seiten oder Seitengruppen können ihre eigenen Manifeste haben, die Dateien aus ihren eigenen spezifischen Verzeichnissen laden. Sprockets kombiniert die von geladenen Dateien automatisch application.jsmit Ihren seitenspezifischen Dateien, sodass diese Lösung funktioniert.

Diese Technik kann auch verwendet werden style_sheets/.

ClosureCowboy
quelle
13
Du hast mich jetzt dazu gebracht, mich nach Cupcakes zu sehnen. Verdammt!
Chuck Bergeron
Diese Lösung gefällt mir sehr gut. Das einzige Problem, das ich damit habe, ist, dass diese zusätzlichen Manifeste nicht komprimiert / hässlich sind. Sie sind jedoch ordnungsgemäß kompiliert. Gibt es eine Lösung oder fehlt mir etwas?
Clst
1
Bedeutet dies, dass der Browser eine js-Datei lädt, dh eine Kombination aus globaler + seitenspezifischer Datei?
Lulalala
Könnten Sie sich meine Frage ansehen, wenn Sie verfügbar sind? stackoverflow.com/questions/17055213/…
Maximus S
1
@clst Ich glaube, das ist die Antwort, die Sie suchen: Guides.rubyonrails.org/asset_pipeline.html#precompiling-assets
FrontierPsycho
23

Ich schätze alle Antworten ... und ich glaube nicht, dass sie das Problem wirklich angehen. Einige von ihnen handeln vom Styling und scheinen nichts damit zu tun zu haben ... andere erwähnen es nurjavascript_include_tag ... was ich weiß (offensichtlich ...), aber es scheint, dass der Weg von Rails 3.1 in Zukunft darin besteht, alles zusammenzufassen Ihr Javascript in 1 Datei, anstatt einzelnes Javascript am Ende jeder Seite zu laden.

Die beste Lösung, die ich finden kann, besteht darin, bestimmte Funktionen in divTags mit ids oder classes zu verpacken . Im Javascript-Code. Dann überprüfen Sie einfach, ob sich das idoder classauf der Seite befindet, und wenn dies der Fall ist, führen Sie den zugehörigen Javascript-Code aus. Auf diese Weise wird der Javascript-Code nicht ausgeführt, wenn sich das dynamische Element nicht auf der Seite befindet - obwohl er in der application.jsvon Sprockets gepackten massiven Datei enthalten ist.

Meine obige Lösung hat den Vorteil, dass ein Suchfeld, das auf 8 der 100 Seiten enthalten ist, nur auf diesen 8 Seiten ausgeführt wird. Sie müssen auch nicht den gleichen Code auf 8 Seiten der Site einfügen. Tatsächlich müssen Sie nie wieder irgendwo manuelle Skript-Tags auf Ihrer Site einfügen - außer um möglicherweise Daten vorab zu laden.

Ich denke, das ist die eigentliche Antwort auf meine Frage.

Feuerzeichen
quelle
Aber Sie wollen tatsächlich diese manuellen <script>Tags. Ja, Klassen und IDs sind Teil der Antwort, aber es macht für den Benutzer keinen Sinn, JavaScript zu laden, das für diese bestimmte Seite nicht erforderlich ist.
Marnen Laibow-Koser
4
@ MarnenLaibow-Koser Der Grund dafür, dass nicht jeder einzelnen Seite manuelle Skript-Tags hinzugefügt werden, besteht darin, dass Sie diesen Skriptinhalt bei jeder Seitenansicht herunterladen müssen. Wenn Sie in der Lage sind, das gesamte Javascript mithilfe der Asset-Pipeline in application.js zu
packen,
@jakeonrails "Der Grund dafür, dass nicht jeder einzelnen Seite manuelle Skript-Tags hinzugefügt werden, besteht darin, dass Sie diesen Skriptinhalt bei jeder Seitenansicht herunterladen müssen" - ganz falsch. Das Skript wird einmal heruntergeladen und bei weiteren Anforderungen aus dem Cache des Browsers abgerufen. "Wenn Sie in der Lage sind, das gesamte Javascript mithilfe der Asset-Pipeline in application.js zu packen, lädt der Benutzer diese Skripte nur einmal herunter" - wahr, aber auf Kosten vieler unnötiger Codes. Wenn Sie Ihr JS in viele kleine Dateien anstatt in eine große strukturieren können, erhalten Sie Caching-Vorteile ohne unnötigen Code.
Marnen Laibow-Koser
1
@ MarnenLaibow-Koser Ich denke, es wäre besser zu sagen, dass Ihr Benutzer nur 1 Skript für eine Seite Ihrer Website herunterladen muss, wenn Sie alles in ein Skript packen. Wenn Sie mehrere Skripte für verschiedene Teile Ihrer App haben, muss der Benutzer offensichtlich mehr als ein Skript herunterladen. Beide Methoden werden natürlich zwischengespeichert, aber in den meisten Fällen (kleine bis mittlere Apps) ist das einmalige Bereitstellen einer Anwendung effizienter. Das Parsen des JS kann eine andere Geschichte sein, je nachdem, was Sie servieren.
Jakeonrails
1
@Ziggy Wenn die kleine Datei nur auf 8 von 100 Seiten verwendet wird, warum sollte der Code dann die ganze Zeit im Cache des Benutzers bleiben? Es ist besser, das nicht benötigte Material fallen zu lassen.
Marnen Laibow-Koser
16

Mir ist klar, dass ich etwas spät zu dieser Party komme, aber ich wollte eine Lösung einbringen, die ich in letzter Zeit verwendet habe. Lassen Sie mich jedoch zuerst erwähnen ...

The Rails 3.1 / 3.2 Way (Nein, Sir. Ich mag es nicht.)

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

Der Vollständigkeit halber füge ich dieser Antwort Folgendes hinzu, und weil es keine unrentable Lösung ist ... obwohl es mir nicht viel ausmacht.

Der "Rails Way" ist eine Controller-orientierte Lösung, anstatt wie der ursprüngliche Autor dieser angeforderten Frage auf die Ansicht ausgerichtet zu sein. Es gibt Controller-spezifische JS-Dateien, die nach ihren jeweiligen Controllern benannt sind. Alle diese Dateien werden in einem Ordnerbaum abgelegt, der standardmäßig NICHT in einer der application.js-Direktiven enthalten ist.

Um Controller-spezifischen Code einzuschließen, wird einer Ansicht Folgendes hinzugefügt.

<%= javascript_include_tag params[:controller] %>

Ich hasse diese Lösung, aber sie ist da und geht schnell. Vermutlich könnten Sie diese Dateien stattdessen so etwas wie "people-index.js" und "people-show.js" nennen und dann so etwas wie "#{params[:controller]}-index"eine ansichtsorientierte Lösung verwenden. Wieder eine schnelle Lösung, aber sie passt nicht gut zu mir.

Mein Datenattribut Weg

Nennen Sie mich verrückt, aber ich möchte, dass ALLE meine JS bei der Bereitstellung kompiliert und in application.js minimiert werden. Ich möchte nicht daran denken müssen, diese kleinen Straggler-Dateien überall einzuschließen.

Ich lade alle meine JS in eine kompakte, bald zwischengespeicherte Browser-Datei. Wenn ein bestimmter Teil meiner application.js auf einer Seite ausgelöst werden muss, lasse ich mich vom HTML-Code informieren, nicht von Rails.

Anstatt mein JS an bestimmte Element-IDs zu sperren oder mein HTML mit Markierungsklassen zu verunreinigen, verwende ich ein benutzerdefiniertes Datenattribut namens data-jstags.

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

Auf jeder Seite verwende ich - hier bevorzugte JS-Bibliotheksmethode einfügen - , um Code auszuführen, wenn das DOM vollständig geladen wurde. Dieser Bootstrapping-Code führt die folgenden Aktionen aus:

  1. Durchlaufen Sie alle Elemente im DOM, die mit gekennzeichnet sind data-jstag
  2. Teilen Sie für jedes Element den Attributwert im Leerzeichen auf und erstellen Sie ein Array von Tag-Zeichenfolgen.
  3. Führen Sie für jede Tag-Zeichenfolge eine Suche in einem Hash für dieses Tag durch.
  4. Wenn ein passender Schlüssel gefunden wird, führen Sie die ihm zugeordnete Funktion aus und übergeben Sie das Element als Parameter.

Angenommen, ich habe irgendwo in meiner application.js Folgendes definiert:

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
};

Das Bootstrapping-Ereignis wendet die Funktionen my_autosuggest_initund auf my_hint_initdie Sucheingabe an und wandelt sie in eine Eingabe um, die eine Liste von Vorschlägen anzeigt, während der Benutzer sie eingibt, und eine Art Eingabehinweis bereitstellt, wenn die Eingabe leer und unscharf bleibt.

Sofern data-jstag="auto-suggest"kein Element mit einem Tag versehen ist , wird der Code für automatische Vorschläge niemals ausgelöst. Es ist jedoch immer vorhanden, minimiert und schließlich in meiner application.js zwischengespeichert, wenn ich es auf einer Seite benötige.

Wenn Sie zusätzliche Parameter an Ihre markierten JS-Funktionen übergeben müssen, müssen Sie etwas Kreativität anwenden. Fügen Sie entweder Datenparameterattribute hinzu, entwickeln Sie eine Parametersyntax oder verwenden Sie sogar einen hybriden Ansatz.

Selbst wenn ich einen komplizierten Workflow habe, der Controller-spezifisch zu sein scheint, erstelle ich einfach eine Datei dafür in meinem lib-Ordner, packe sie in application.js und tagge sie mit etwas wie "new-thing-wizard". Wenn mein Bootstrap auf dieses Tag trifft, wird mein netter, ausgefallener Assistent instanziiert und ausgeführt. Es wird bei Bedarf für die Ansicht (en) dieses Controllers ausgeführt, ist jedoch nicht anderweitig mit dem Controller gekoppelt. Wenn ich meinen Assistenten richtig codiere, kann ich möglicherweise alle Konfigurationsdaten in den Ansichten bereitstellen und meinen Assistenten später für jeden anderen Controller wiederverwenden, der ihn benötigt.

Auf diese Weise implementiere ich seit einiger Zeit seitenspezifisches JS und es hat mir sowohl für einfache Site-Designs als auch für komplexere / umfangreichere Anwendungen gute Dienste geleistet. Hoffentlich ist eine der beiden Lösungen, die ich hier vorgestellt habe, mein Weg oder der Rails-Weg, für jeden hilfreich, der in Zukunft auf diese Frage stößt.

Ryan
quelle
6
Ein kleines Detail: In Ihrer Antwort steht die Vorstellung, dass das js, sobald es im Browser zwischengespeichert ist, keine Auswirkungen mehr hat. Das ist nicht ganz richtig. Der Browser vermeidet zwar den Download, wenn die js-Datei ordnungsgemäß zwischengespeichert ist, kompiliert aber dennoch den Code bei jedem Rendern der Seite. Sie müssen also Kompromisse ausgleichen. Wenn Sie insgesamt viel JS haben, aber nur einige pro Seite verwendet werden, können Sie möglicherweise die Seitenzeiten verbessern, indem Sie das JS auseinander brechen.
Sujal
Weitere Informationen zu den praktischen Auswirkungen dieses Kompilierungsschritts finden Sie in der Erklärung von 37 Signals, wie sich pjax auf Basecamp ausgewirkt hat. Weiter: 37signals.com/svn/posts/…
sujal
Das ist ein fairer Punkt. Nachdem ich den Artikel gelesen und auf Projekte zurückgeblickt habe, in denen ich die oben genannte Lösung verwendet habe, stelle ich fest, dass ich im Wesentlichen dieselbe Lösung zum Senden des geänderten HTML-Codes geschrieben habe, die im Artikel erwähnt wird. Das häufige Neukompilieren von JS war aus diesem Grund in meinen Projekten kein Problem. Der Kompilierungsschritt ist etwas, an das ich denken werde, wenn ich an weniger "Desktop-Anwendungen" orientierten Websites arbeite.
Ryan
2
Downvoting für "Nenn mich verrückt, aber ich möchte, dass ALLE meine JS kompiliert und in application.js minimiert werden, wenn ich sie bereitstelle." Sie möchten dies wirklich nicht, da der Benutzer JavaScript lädt, das er nicht benötigt, und Ihre Handler nach Attributen suchen, die nicht einmal vorhanden sein werden. Alles in app.js zu haben ist verlockend, und Rails macht es sicherlich einfach, aber das Richtige ist, JavaScript besser zu modularisieren.
Marnen Laibow-Koser
Sie haben das Recht auf eine andere Meinung ... und sind technisch berechtigt, über Meinungsverschiedenheiten abzustimmen. Es wäre jedoch schön zu sehen, warum eine große und zwischengespeicherte Datei dem Erzwingen mehrerer HTTP-Anforderungen zum Abrufen von modularisiertem JS unterlegen ist. Darüber hinaus irren Sie sich hinsichtlich der Handler-Suchprozedur. Die Tag-Werte werden NICHT durchsucht. Es wird immer nur eine Suche durchgeführt, und es werden alle Elemente abgerufen, die ein data-jstag-Attribut haben. Es wird nicht nach dem Tag-Namen gesucht, sondern nur alle Elemente mit Tags gefunden und dann nur die benötigten Objekte instanziiert.
Ryan
7

Dies wurde vor langer Zeit beantwortet und akzeptiert, aber ich habe meine eigene Lösung entwickelt, die auf einigen dieser Antworten und meinen Erfahrungen mit Rails 3+ basiert.

Die Asset-Pipeline ist süß. Benutze es.

application.jsEntfernen Sie zunächst in Ihrer Datei//= require_tree.

Dann application_controller.rberstellen Sie in Ihrem eine Hilfsmethode:

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

Fügen Sie dann in Ihrer application.html.erbLayoutdatei Ihren neuen Helfer zu den vorhandenen Javascript-Includes hinzu, denen der rawHelfer vorangestellt ist :

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

Voila, jetzt können Sie ganz einfach ansichtsspezifisches Javascript mit derselben Dateistruktur erstellen, die Sie überall in Rails verwenden. Stecken Sie einfach Ihre Dateien ein app/assets/:namespace/:controller/action.js.erb!

Hoffe das hilft jemand anderem!

Mike A.
quelle
1
Verursacht dies keine Probleme, nachdem Assets vorkompiliert wurden und zur Laufzeit <%= raw ... %>ein 404 zurückgegeben wird?
Nishant
Ich denke, die Asset-Pipeline ist nicht süß, da sie eine Reihe von Dateien erstellt, die oft nicht verwendet werden sollten. Wenn ich mich auf die Asset-Pipeline verlasse, entsteht für mich eine Abhängigkeit von einem ineffizienten System.
Deborah
1
@DeborahSpeece Wann erstellt die Asset-Pipeline Dateien, die nicht verwendet werden sollten? Verwechseln Sie die Asset-Pipeline (gut) mit require_tree /(schlecht)?
Marnen Laibow-Koser
6

Sie können diese Zeile in Ihre Layoutdatei (z. B. application.html.erb) einfügen, um die Controller-spezifische Javascript-Datei (die beim Generieren des Controllers erstellt wurde) automatisch zu laden:

<%= javascript_include_tag params[:controller] %>

Sie können auch eine Zeile hinzufügen, um eine Skriptdatei pro Aktion automatisch zu laden.

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

Fügen Sie einfach Ihre Seitenskripte in eine Unterverzeichnis ein, die nach dem Namen des Controllers benannt ist. In diese Dateien können Sie andere Skripte mit = require einfügen. Es wäre schön, einen Helfer zu erstellen, der die Datei nur dann einschließt, wenn sie vorhanden ist, um einen 404-Fehler im Browser zu vermeiden.

mcubik
quelle
6
<%= javascript_include_tag params[:controller] %>
Herr Bohr
quelle
2
Dies scheint in der Lage zu sein, die Frage zu beantworten. Könnten Sie bitte der Antwort mehr hinzufügen, um sie zu konkretisieren?
6

Vielleicht finden Sie pluggable_js Juwel als geeignete Lösung.

peresleguine
quelle
5

Das LoadJS- Juwel ist eine weitere Option:

LoadJS bietet eine Möglichkeit, seitenspezifischen Javascript-Code in eine Rails-App zu laden, ohne die Magie von Sprockets zu verlieren. Ihr gesamter Javascript-Code wird weiterhin in einer Javascript-Datei minimiert, aber einige Teile davon werden nur für bestimmte Seiten ausgeführt.

https://github.com/guidomb/loadjs

meleyal
quelle
3

Philipps Antwort ist ziemlich gut. Hier ist der Code, damit es funktioniert:

In application.html.erb:

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

Angenommen, Ihr Controller heißt Projekte, wird Folgendes generiert:

<body class="projects">

Dann in projects.js.coffee:

jQuery ->
  if $('body.projects').length > 0  
     $('h1').click ->
       alert 'you clicked on an h1 in Projects'
Rahil Sondhi
quelle
Downvoting: Jede Lösung, die eine Klasse auf die setzt, <body>ist ipso facto falsch. Siehe meine Kommentare an anderer Stelle auf dieser Seite.
Marnen Laibow-Koser
Tu das nicht. Das Problem hierbei ist, dass Sie jedes Mal, wenn Sie eines davon hinzufügen, ein weiteres Stück js hinzufügen, das beim Laden der Seite ausgeführt werden muss. Könnte definitiv zu Leistungseinbußen führen, wenn Ihr Projekt wächst.
ifightcrime
2

JavaScripts werden nur zusammengeführt, wenn Sie Rails (Sprockets) anweisen, sie zusammenzuführen.

Yfeldblum
quelle
Na sicher. Ich frage wohl, weil die Standardeinstellungen von Rails alles im Ordner enthalten ... was bedeutet, dass David beabsichtigt, dass Sie das tun. Aber wie ich in dem anderen Kommentar zu @rubyprince sagte, bin ich mir nicht sicher, ob die Ausführung so erfolgt. Ich denke ich muss deaktivieren //= require_tree .?
Feuer Emblem
@FireEmblem Ja. require_tree .ist normalerweise eine schlechte Idee.
Marnen Laibow-Koser
2

So habe ich das Styling-Problem gelöst: (Entschuldigung der Haml)

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

Auf diese Weise starte ich alle seitenspezifischen .css.sass- Dateien mit:

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

Auf diese Weise können Sie leicht Zusammenstöße vermeiden. Wenn es um .js.coffee- Dateien geht, können Sie einfach Elemente wie;

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

Hoffe das hat einigen geholfen.

Zeeraw
quelle
1
Lesen Sie das letzte Bit noch einmal durch. Für Javascript können Sie dieselbe Struktur verwenden, die für Stylesheets zum Initialisieren von ansichtsspezifischen Funktionen verwendet wird.
Zeeraw
Philip $('#post > #edit') ->scheint ungültig zu sein. Wie kann jQuery innerhalb eines Bereichs arbeiten?
Ramon Tayag
2
Vor kurzem habe ich begonnen, alle Controller-spezifischen Java-Skripte und Stylesheets zu laden, indem ich dies in der application.html.haml aufgerufen habe. = javascript_include_tag "application"Auf = javascript_include_tag params[:controller]diese Weise kann ich Javascript-Code beschränken, ohne einen Bereich in der Datei angeben zu müssen.
Zeeraw
2

Ich stimme Ihrer Antwort zu. Um zu überprüfen, ob dieser Selektor vorhanden ist, verwenden Sie:

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

(Ich habe nicht gesehen, dass jemand die eigentliche Lösung hinzugefügt hat)

Kyle C.
quelle
2

Ich sehe keine Antwort, die wirklich alles zusammenfügt und für Sie auslegt. Ich werde also versuchen zu setzen meleyal , Sujal (a la ClosureCowboy ), den ersten Teil der Ryans Antwort, und sogar Gals mutige Aussage über Backbone.js ... alle zusammen in einer Weise , die kurz und klar. Und wer weiß, ich könnte sogar die Anforderungen von Marnen Laibow-Koser erfüllen.

Beispieländerungen

Assets / Javascripts / application.js

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


Ansichten / 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>


Ansichten / foo / index.html.erb

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


Assets / Javascripts / foo.js

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


Assets / Javascripts / Foostuff / Foothis.js.coffee

alert "Hello world!"


Kurze Beschreibung

  • Entfernen Sie //= require_tree .aus application.js und listen Sie nur die JS auf, die jede Seite gemeinsam nutzt.

  • Die beiden oben in application.html.erb gezeigten Zeilen geben der Seite an, wo application.js und Ihr seitenspezifisches JS enthalten sein sollen.

  • Die drei oben in index.html.erb gezeigten Zeilen weisen Ihre Ansicht an, nach einem seitenspezifischen JS zu suchen und ihn in einen benannten Ertragsbereich mit dem Namen ": javascript" (oder wie auch immer Sie ihn benennen möchten) aufzunehmen. In diesem Beispiel ist der Controller "foo", daher versucht Rails, "foo.js" im Ertragsbereich: javascript in das Anwendungslayout aufzunehmen.

  • Listen Sie Ihr seitenspezifisches JS in foo.js auf (oder wie auch immer der Controller heißt). Listen Sie allgemeine Bibliotheken, einen Baum, Verzeichnisse usw. auf.

  • Bewahren Sie Ihr benutzerdefiniertes seitenspezifisches JS an einem Ort auf, an dem Sie es neben Ihrem anderen benutzerdefinierten JS problemlos referenzieren können. In diesem Beispiel benötigt foo.js den Foostuff-Baum. Platzieren Sie also Ihr benutzerdefiniertes JS dort, z. B. foothis.js.coffee .

  • Hier gibt es keine harten Regeln. Sie können die Dinge jederzeit verschieben und bei Bedarf sogar mehrere Ertragsbereiche mit verschiedenen Namen in verschiedenen Layouts erstellen. Dies zeigt nur einen möglichen ersten Schritt vorwärts. (Ich mache es nicht genau so, da wir Backbone.js verwenden. Ich könnte auch foo.js in einem Ordner namens foo anstelle von foostuff ablegen, habe das aber noch nicht entschieden.)

Anmerkungen

Sie können ähnliche Dinge mit CSS tun, <%= stylesheet_link_tag params[:controller] %>aber dies geht über den Rahmen der Frage hinaus.

Wenn ich hier eine krasse Best Practice verpasst habe, sende mir eine Notiz und ich werde mich anpassen. Rails ist für mich ziemlich neu und ehrlich gesagt bin ich bisher nicht sonderlich beeindruckt von dem Chaos, das es standardmäßig für die Unternehmensentwicklung mit sich bringt, und dem gesamten Datenverkehr, den das durchschnittliche Rails-Programm generiert.

Juanitogan
quelle
Das scheint der richtige Weg zu sein. Ich werde sehen, ob ich es in meiner eigenen App implementieren kann, danke für die ausführliche Antwort.
Martincarlin87
1

Ich habe eine andere Lösung, die zwar primitiv für mich funktioniert und keine ausgefallenen selektiven Ladestrategien benötigt. Fügen Sie Ihre Nornal Document Ready-Funktion ein, testen Sie dann den aktuellen Windows-Speicherort, um festzustellen, ob es sich um die Seite handelt, für die Ihr Javascript bestimmt ist:

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

Dies ermöglicht weiterhin, dass alle js von Rails 3.x in einem kleinen Paket geladen werden, erzeugt jedoch nicht viel Overhead oder Konflikte mit Seiten, für die das js nicht vorgesehen ist.

Peter Abramowitsch
quelle
1

Ryguys Antwort ist eine gute Antwort, obwohl sie in negative Punkte herabgestuft wurde.

Besonders wenn Sie so etwas wie Backbone JS verwenden - jede Seite hat ihre eigene Backbone-Ansicht. Dann hat die erb-Datei nur eine einzige Zeile Inline-Javascript, die die rechte Backbone-Ansichtsklasse auslöst. Ich betrachte es als eine einzelne Zeile 'Klebercode' und daher die Tatsache, dass seine Inline in Ordnung ist. Der Vorteil ist, dass Sie Ihren "require_tree" behalten können, mit dem der Browser das gesamte Javascript zwischenspeichern kann.

In show.html.erb haben Sie so etwas wie:

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

und in Ihrer Layoutdatei benötigen Sie:

<%= yield :javascript %>
Gal
quelle
Downvoting. Inline-JavaScript ist niemals eine gute Idee. Auch wenn es sich um Klebercode handelt, sollte er sich in einer externen Datei befinden.
Marnen Laibow-Koser
1

Verschieben Sie alle Ihre Commom-JS-Dateien in einen Unterordner wie 'app / assets / javascript / global' und ändern Sie dann in der application.js die //= require_tree .Zeile in //= require_tree ./global.

Jetzt können Sie Ihr Controller-spezifisches JS im Stammverzeichnis 'app / assets / javascript /' ablegen. Diese werden nicht in das kompilierte JS aufgenommen und nur verwendet, wenn Sie sie über = javascript_include_tagIhren Controller / Ihre Ansicht aufrufen .

Gedean Dias
quelle
Auf keinen Fall, das ist ein Crapload von JavaScript, das für eine Seite geladen werden muss. Es spielt keine Rolle, ob es zwischengespeichert ist.
Jackyalcine
1

Obwohl Sie hier mehrere Antworten haben, denke ich, dass Ihre Bearbeitung wahrscheinlich die beste Wahl ist. Ein Designmuster, das wir in unserem Team von Gitlab verwenden, ist das Dispatcher-Muster. Es macht etwas Ähnliches wie das, wovon Sie sprechen, jedoch wird der Seitenname im Body-Tag durch Schienen festgelegt. Fügen Sie beispielsweise in Ihre Layoutdatei einfach Folgendes ein (in HAML):

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

Dann haben Sie nur einen Abschluss und eine switch-Anweisung in Ihrer dispatcher.js.coffeeDatei in Ihrem Javascripts-Ordner wie folgt:

$ ->
  new Dispatcher()

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

Alles, was Sie in den einzelnen Dateien tun müssen ( z. B. products.js.coffeeoder login.js.coffeezum Beispiel), ist, sie in eine Klasse einzuschließen und dieses Klassensymbol dann zu globalisieren, damit Sie im Dispatcher darauf zugreifen können:

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

Gitlab hat einige Beispiele dafür, mit denen Sie vielleicht herumstöbern möchten, falls Sie neugierig sind :)

onetwopunch
quelle
1

Das Paloma- Projekt bietet einen interessanten Ansatz zur Verwaltung von seitenspezifischem Javascript-Code.

Verwendungsbeispiel aus ihren Dokumenten:

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

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

Schritt 1. entferne require_tree. in Ihrer application.js und application.css.

Schritt 2. Bearbeiten Sie Ihre application.html.erb (standardmäßig auf Schienen) im Layoutordner. Fügen Sie "params [: controller]" in die folgenden Tags ein.

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

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

Schritt 3. Fügen Sie eine Datei in config / initializers / assets.rb hinzu

%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

Referenzen: http://theflyingdeveloper.com/controller-specific-assets-with-rails-4/

etlds
quelle
Während dies theoretisch die Frage beantworten kann, wäre es vorzuziehen , die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen.
Bhargav Rao
0

Ich habe das noch nicht ausprobiert, aber es sieht so aus, als ob Folgendes wahr ist:

  • Wenn Sie ein content_for haben, das Javascript ist (z. B. mit echtem Javascript darin), würden Kettenräder nichts davon wissen, und daher würde dies genauso funktionieren wie jetzt.

  • Wenn Sie eine Datei aus dem großen Javascript-Paket ausschließen möchten, gehen Sie in die Datei config / sprockets.yml und ändern Sie die Quelldateien entsprechend. Dann würden Sie einfach alle Dateien einschließen, die Sie bei Bedarf ausgeschlossen haben.

Bill Eisenhauer
quelle
Ist das Ausschließen von Dateien oder die Verwendung von benutzerdefiniertem Javascript auf der Seite selbst dann der "richtige Weg"? Wollte David so, dass die Leute es benutzen?
Feuer Emblem
@FireEmblem Es ist mir egal, was David beabsichtigt hat, weil ich nicht glaube, dass David versteht, wie man JavaScript richtig organisiert.
Marnen Laibow-Koser
0

Ich habe einige Antworten kombiniert in:

Anwendungshelfer:

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
quelle
0

Erstens: \\=require_treeAus application.js entfernen. Zweitens: Der gesamte JS-Code muss unter /app/assets/javascritptund der gesamte CSS-Code unter zugeordnet sein/app/assets/stylesheets

Nicollas
quelle
-2

Nach der Führung von Ryan habe ich Folgendes getan:

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 (Controller-spezifisches Kaffeeskript, z. B. Controller: Benutzer, Aktion: Dashboard)

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
quelle
Downvoting. Dies ist lächerlich kompliziert - ganz zu schweigen von der Unsicherheit (aufgrund der eval), wenn Ihr HTML-Code durch einen geknackten Server oder ein bösartiges Benutzer-Skript kompromittiert wird.
Marnen Laibow-Koser
-3

Dies geschieht insbesondere dann, wenn Sie nicht Tonnen von Bibliotheken für Ihre spezifische Seite ausführen müssen, sondern nur ein paar hundert Zeilen JS mehr oder weniger ausführen müssen.

Da es vollkommen in Ordnung ist, Javascript-Code in HTML einzubetten, erstellen Sie einfach unter dem Verzeichnis app / views shared.js und platzieren Sie dort Ihren seiten- / seitenspezifischen Code in my_cool_partial.html.erb

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

Jetzt tun Sie einfach, wo immer Sie wollen:

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

Und das war's, k?

valk
quelle
2
Downvoting. Inline-JavaScript ist niemals ratsam. HTML sollte nur Markup enthalten. JS und CSS sollten sich in separaten, wiederverwendbaren Dateien befinden.
Marnen Laibow-Koser