Используя Rails 3.1, куда вы помещаете свой "специфичный для страницы" код JavaScript?

Насколько я понимаю, весь ваш JavaScript объединяется в 1 файл. Rails делает это по умолчанию, когда добавляет //= require_tree . в нижней части вашего application.js файл манифеста.

Это звучит как реальный спаситель жизни, но я немного обеспокоен специфичным для страницы кодом JavaScript. Этот код выполняется на каждой странице? Последнее, что я хочу, - чтобы все мои объекты создавались для каждой страницы, когда они нужны только на 1 странице.

Кроме того, не существует ли потенциал для кода, который также конфликтует?

Или вы положили маленький script тег внизу страницы, который просто вызывает метод, который выполняет код JavaScript для страницы?

Вам больше не нужен require.js?

Спасибо

РЕДАКТИРОВАТЬ: Я ценю все ответы... и я не думаю, что они действительно решают проблему. Некоторые из них касаются стиля и, кажется, не связаны... а другие просто упоминают javascript_include_tag... который, как я знаю, существует (очевидно...), но может показаться, что в будущем для Rails 3.1 нужно заключить весь ваш JavaScript в 1 файл, а не загружать отдельный JavaScript в нижней части каждой страницы.

Лучшее решение, которое я могу придумать, - это обернуть некоторые функции в div теги с idс или classэс. В коде JavaScript вы просто проверяете, id или же class находится на странице, и если это так, вы запускаете связанный с ним код JavaScript. Таким образом, если динамический элемент отсутствует на странице, код JavaScript не запускается, даже если он включен в массовый application.js файл упакован Sprockets.

Мое вышеупомянутое решение имеет то преимущество, что если окно поиска включено на 8 из 100 страниц, оно будет работать только на этих 8 страницах. Вам также не придется включать этот код на 8 страницах сайта. На самом деле, вам больше никогда не придется добавлять теги сценариев вручную на ваш сайт.

Я думаю, что это фактический ответ на мой вопрос.

29 ответов

Решение

Я ценю все ответы... и я не думаю, что они действительно решают проблему. Некоторые из них касаются стиля и, кажется, не связаны... а другие просто упоминают javascript_include_tag... который, как я знаю, существует (очевидно...), но может показаться, что в будущем для Rails 3.1 необходимо заключить весь ваш Javascript в 1 файл, а не загружать отдельный Javascript внизу каждой страницы.

Лучшее решение, которое я могу придумать, - это обернуть некоторые функции в div теги с idс или classэс. В коде JavaScript. Тогда вы просто проверьте, если id или же class находится на странице, и если это так, вы запускаете связанный с ним код JavaScript. Таким образом, если динамический элемент отсутствует на странице, код javascript не запускается - даже если он включен в массовый application.js файл упакован Sprockets.

Мое вышеупомянутое решение имеет то преимущество, что если окно поиска включено на 8 из 100 страниц, оно будет работать только на этих 8 страницах. Вам также не придется включать этот код на 8 страницах сайта. На самом деле, вам никогда больше не придется включать теги сценариев вручную на ваш сайт, кроме как для предварительной загрузки данных.

Я думаю, что это фактический ответ на мой вопрос.

Документы Asset Pipeline предлагают, как сделать JS для конкретного контроллера:

Например, если ProjectsController будет создан новый файл в app/assets/javascripts/projects.js.coffee и другой в app/assets/stylesheets/projects.css.scss, Вы должны поместить любой JavaScript или CSS, уникальный для контроллера, в их соответствующие файлы ресурсов, так как эти файлы могут быть загружены только для этих контроллеров с такими строками, как <%= javascript_include_tag params[:controller] %> или же <%= stylesheet_link_tag params[:controller] %>,

Ссылка на: asset_pipeline

Для js для конкретной страницы вы можете использовать решение Garber-Irish.

Таким образом, ваша папка Rails javascripts может выглядеть так для двух контроллеров - автомобилей и пользователей:

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

И javascripts будет выглядеть так:

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

и markup_based_js_execution будет содержать код для объекта UTIL и для выполнения UTIL.init с поддержкой DOM.

И не забудьте поместить это в свой файл макета:

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

Я также думаю, что лучше использовать классы вместо data-* атрибуты, для лучшего CSS для конкретной страницы. Как упоминал Джейсон Гарбер: CSS-селекторы для конкретной страницы могут быть очень неудобными (когда вы используете data-*атрибуты)

Я надеюсь, что это поможет вам.

Я вижу, что вы ответили на свой вопрос, но вот еще один вариант:

По сути, вы делаете предположение, что

//= require_tree .

необходимо. Это не. Не стесняйтесь удалить это. В моем текущем приложении, честно говоря, первое, что я делаю с 3.1.x, я сделал три разных JS-файла верхнего уровня. мой application.js только файл имеет

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

Таким образом, я могу создавать подкаталоги с их собственными файлами JS верхнего уровня, которые содержат только то, что мне нужно.

Ключи:

  1. Вы можете удалить require_tree - Rails позволяет вам изменять предположения, которые он делает
  2. Там нет ничего особенного в названии application.js - любой файл в assets/javascript подкаталог может включать директивы препроцессора с //=

Надеюсь, что это помогает и добавляет некоторые детали к ответу ClosureCowboy.

Sujal

Другой вариант: для создания файлов, специфичных для страницы или модели, вы можете создать каталоги внутри assets/javascripts/ папка.

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

Ваш главный application.js файл манифеста может быть настроен для загрузки его файлов из global/, Определенные страницы или группы страниц могут иметь свои собственные манифесты, которые загружают файлы из их собственных определенных каталогов. Звездочки автоматически объединят файлы, загруженные application.js с вашими специфичными для страницы файлами, что позволяет этому решению работать.

Эта техника может быть использована для style_sheets/ также.

Я понимаю, что прихожу на эту вечеринку немного поздно, но я хотел добавить решение, которое я использовал в последнее время. Тем не менее, позвольте мне сначала упомянуть...

Rails 3.1/3.2 Way (Нет, сэр. Мне это не нравится.)

Смотрите: http://guides.rubyonrails.org/asset_pipeline.html

Для полноты изложения я включаю следующее и потому, что это не является нежизнеспособным решением... хотя меня это не волнует.

"Rails Way" - это решение, ориентированное на контроллеры, а не ориентированное на просмотр, как просил первоначальный автор этого вопроса. Существуют специфичные для контроллера файлы JS, названные в честь соответствующих контроллеров. Все эти файлы помещаются в дерево папок, которое по умолчанию НЕ включено ни в одно из приложений.js требуют директив.

Чтобы включить специфичный для контроллера код, в представление добавляется следующее.

<%= javascript_include_tag params[:controller] %>

Я ненавижу это решение, но оно есть и оно быстрое. Предположительно, вы могли бы вместо этого назвать эти файлы как "people-index.js" и "people-show.js", а затем использовать что-то вроде "#{params[:controller]}-index" чтобы получить ориентированное на вид решение. Опять же, быстрое решение, но мне это не подходит.

Мой путь к данным

Считай меня сумасшедшим, но я хочу, чтобы ВСЕ мои JS компилировались и минимизировались в application.js при развертывании. Я не хочу помнить, чтобы включить эти маленькие файлы бродяги повсюду.

Я загружаю все свои JS в один компактный файл, который скоро будет кэширован браузером. Если какой-то кусок моего application.js должен быть запущен на странице, я позволяю HTML сказать мне, а не Rails.

Вместо того, чтобы привязывать мой JS к определенным идентификаторам элементов или засорять мой HTML классами маркеров, я использую пользовательский атрибут данных, который называется data-jstags,

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

На каждой странице я использую - вставьте предпочтительный метод библиотеки JS здесь - для запуска кода после завершения загрузки DOM. Этот код начальной загрузки выполняет следующие действия:

  1. Перебирать все элементы в DOM, отмеченные data-jstag
  2. Для каждого элемента разбейте значение атрибута на пробел, создав массив строк тегов.
  3. Для каждой строки тега выполните поиск в хэше для этого тега.
  4. Если соответствующий ключ найден, запустите связанную с ним функцию, передав элемент в качестве параметра.

Скажем, у меня в файле 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
};

Событие начальной загрузки собирается применить my_autosuggest_init а также my_hint_init работает против входных данных поиска, превращая его во входные данные, которые отображают список предложений, когда пользователь вводит данные, а также предоставляет некоторую подсказку для ввода, когда ввод остается пустым и не сфокусированным.

Если какой-то элемент не помечен data-jstag="auto-suggest"код автоматического предложения никогда не срабатывает. Тем не менее, он всегда присутствует, минимизируется и в конечном итоге кэшируется в моем application.js для тех случаев, когда он мне нужен на странице.

Если вам нужно передать дополнительные параметры своим теговым функциям JS, вам придется применить некоторые творческие возможности. Либо добавьте атрибуты data-paramter, придумайте некоторый синтаксис параметров или даже используйте гибридный подход.

Даже если у меня есть какой-то сложный рабочий процесс, который кажется специфичным для контроллера, я просто создам файл для него в своей папке lib, запакую его в application.js и добавлю к нему что-то вроде 'new-thing-wizard'. Когда мой загрузчик попадет в этот тег, будет создан и запущен мой красивый, причудливый волшебник. Он работает для представления (ов) этого контроллера, когда это необходимо, но никак не связан с контроллером. Фактически, если я правильно закодирую свой мастер, я смогу предоставить все данные конфигурации в представлениях и, следовательно, позже смогу повторно использовать мой мастер для любого другого контроллера, который нуждается в нем.

Как бы то ни было, именно так я уже давно внедряю JS для конкретных страниц, и это хорошо мне помогло как для простого дизайна сайта, так и для более сложных / насыщенных приложений. Надеемся, что одно из двух решений, которые я представил здесь, мой путь или путь Rails, будет полезно всем, кто сталкивается с этим вопросом в будущем.

На это уже давно ответили и приняли, но я придумал собственное решение, основанное на некоторых из этих ответов и моем опыте работы с Rails 3+.

Трубопровод активов сладок. Используй это.

Во-первых, в вашем application.js файл, удалить //= require_tree.

Тогда в вашем application_controller.rb создать вспомогательный метод:

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

Тогда в вашем application.html.erb файл макета, добавьте ваш новый помощник среди существующих включений JavaScript, с префиксом raw помощник:

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

Вуаля, теперь вы можете легко создавать специфичные для представления javascript, используя ту же файловую структуру, которую вы используете везде в rails. Просто вставьте свои файлы в app/assets/:namespace/:controller/action.js.erb!

Надеюсь, что это помогает кому-то еще!

Может быть, вы найдете гем pluggable_js как подходящее решение.

Вы можете добавить эту строку в свой файл макета (например, application.html.erb), чтобы автоматически загрузить специфичный для контроллера файл javascript (тот, который был создан при создании контроллера):

<%= javascript_include_tag params[:controller] %>

Вы также можете добавить строку для автоматической загрузки файла сценария для каждого действия.

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

Просто поместите скрипты вашей страницы в подкаталог, названный в честь имени контроллера. В эти файлы вы можете включить другие скрипты, используя = require. Было бы неплохо создать помощника для включения файла, только если он существует, чтобы избежать сбоя 404 в браузере.

<%= javascript_include_tag params[:controller] %>

LoadJS gem это еще один вариант:

LoadJS предоставляет способ загрузки специфичного для страницы кода Javascript в приложении Rails без потери волшебства, предоставляемого Sprockets. Весь ваш код Javascript будет продолжен в одном файле Javascript, но некоторые его части будут выполняться только для определенных страниц.

https://github.com/guidomb/loadjs

Ответ Филиппа довольно хороший. Вот код, чтобы заставить это работать:

В application.html.erb:

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

Предполагая, что ваш контроллер называется Projects, он сгенерирует:

<body class="projects">

Тогда в projects.js.coffee:

jQuery ->
  if $('body.projects').length > 0  
     $('h1').click ->
       alert 'you clicked on an h1 in Projects'

JavaScripts объединяются только тогда, когда вы указываете Rails (скорее Sprockets) объединить их.

Я не вижу ответа, который действительно объединяет все это и выкладывает это для вас. Таким образом, я постараюсь соединить meleyal, sujal (a la ClosureCowboy), первую часть ответа Райана и даже смелое утверждение Гэла о Backbone.js... все вместе кратким и ясным образом. И, кто знает, я мог бы даже удовлетворить требования Марнена Лайбоу-Козера.

Пример редактирования

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


assets / javascripts / foostuff / foothis.js.coffee

alert "Hello world!"


Краткое описание

  • Удалить //= require_tree . из application.js и перечислите только тот JS, который разделяет каждая страница.

  • Две строки, показанные выше в application.html.erb, указывают странице, куда следует включить application.js и ваш JS для конкретной страницы.

  • Три строки, показанные выше в index.html.erb, говорят вашему представлению о поиске некоторого JS для конкретной страницы и включении его в именованную область выхода, называемую ":javascript" (или как вы хотите ее назвать). В этом примере контроллером является "foo", поэтому Rails попытается включить "foo.js" в область выхода: javascript в макете приложения.

  • Перечислите свой JS для конкретной страницы в файле foo.js (или как называется контроллер). Список общих библиотек, дерево, каталоги, что угодно.

  • Держите свой JS для конкретной страницы где-нибудь, где вы можете легко ссылаться на него отдельно от вашего другого JS. В этом примере для foo.js требуется дерево foostuff, поэтому поместите туда свой пользовательский JS, например foothis.js.coffee.

  • Здесь нет жестких правил. Не стесняйтесь перемещать вещи и, возможно, даже создавать несколько областей доходности с различными именами в различных макетах, если это необходимо. Это просто показывает один возможный первый шаг вперед. (Я делаю это не совсем так, учитывая наше использование Backbone.js. Я мог бы также поместить foo.js в папку с именем foo вместо foostuff, но пока не решил).

Заметки

Вы можете делать подобные вещи с помощью CSS и <%= stylesheet_link_tag params[:controller] %> но это выходит за рамки вопроса.

Если я пропустил яркую лучшую практику здесь, пришли мне записку, и я придумаю адаптацию. Rails довольно нов для меня, и, честно говоря, я до сих пор не очень впечатлен хаосом, который он по умолчанию вносит в развитие предприятия и весь трафик, который генерирует средняя программа Rails.

Вы также можете сгруппировать js по папкам и продолжать использовать конвейер ресурсов для выборочной загрузки вашего javascript в зависимости от страницы.

Я согласен с вашим ответом, чтобы проверить, есть ли этот селектор, используйте:

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

(никто не видел, чтобы добавить фактическое решение)

Вот как я решил проблему стайлинга: (извините, Haml)

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

Таким образом, я запускаю все специфичные для страницы файлы .css.sass с:

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

Таким образом, вы можете легко избежать любых столкновений. Когда дело доходит до файлов .js.coffee, вы можете просто инициализировать такие элементы, как;

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

Надеюсь, это помогло некоторым.

Хотя у вас есть несколько ответов здесь, я думаю, что ваша редакция, вероятно, лучшая ставка. Шаблон проектирования, который мы используем в нашей команде, который мы получили от Gitlab, - это шаблон Dispatcher. Он делает что-то похожее на то, о чем вы говорите, однако имя страницы задается в теге body с помощью rails. Например, в вашем файле макета просто включите что-то вроде (в HAML):

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

Тогда есть только одно замыкание и оператор switch в вашем dispatcher.js.coffee файл в папке javascripts примерно так:

$ ->
  new Dispatcher()

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

Все, что вам нужно сделать в отдельных файлах (скажем, products.js.coffee или же login.js.coffee например) заключает их в класс и затем глобализирует этот символ класса, чтобы вы могли получить к нему доступ в диспетчере:

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

У Gitlab есть несколько примеров этого, с которыми вы можете поэкспериментировать, если вам интересно:)

Шаг 1. удалить require_tree . в вашем application.js и application.css.

Шаг 2. Отредактируйте файл application.html.erb(по умолчанию на rails) в папке макета. Добавьте "params[:controller]" в следующие теги.

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

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

Шаг 3. Добавьте файл в 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

ссылки: http://theflyingdeveloper.com/controller-specific-assets-with-rails-4/

У меня есть другое решение, которое, хотя примитив отлично работает для меня и не нуждается в каких-либо необычных выборочных стратегиях загрузки. Поместите в свою обычную функцию готовности документа, но затем проверьте текущее расположение окон, чтобы увидеть, предназначена ли это страница для вашего javascript:

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

Это по-прежнему позволяет загружать все js с помощью rails 3.x в одном небольшом пакете, но не создает больших накладных расходов или конфликтов со страницами, для которых js не предназначен.

Переместите все ваши JS-файлы commom в подпапку, например "app / assets / javascript / global", затем в application.js, измените //= require_tree . линия к //= require_tree ./global,

Теперь вы можете поместить свой JS для конкретного контроллера в корень "app / assets / javascript /", и они не будут включены в скомпилированный JS, а используются только тогда, когда вы вызываете их через = javascript_include_tag на вашем контроллере / просмотр.

Проект Paloma предлагает интересный подход к управлению javascript-кодом для конкретной страницы.

Пример использования из их документов:

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

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

Ответ Ryguy является хорошим ответом, даже если его оценили как негативную точку.

Особенно, если вы используете что-то вроде Backbone JS - каждая страница имеет свой собственный вид Backbone. Тогда у файла erb есть только одна строка встроенного javascript, который запускает правый базовый класс представления. Я считаю, что это единственная строка "склеивающего кода" и, следовательно, тот факт, что с ней все в порядке. Преимущество заключается в том, что вы можете сохранить ваше "require_tree", которое позволяет браузеру кэшировать весь javascript.

в show.html.erb у вас будет что-то вроде:

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

и в вашем файле макета вам понадобится:

<%= yield :javascript %>

Я делал это ранее, используя этот метод: http://theflyingdeveloper.com/controller-specific-assets-with-rails-4/. Супер-легкий, полагается на контроллеры, чтобы выбрать правильный JS для загрузки.

Сначала удалите \\=require_treeиз application.js Второе: весь ваш код JS должен быть размещен на /app/assets/javascritpt и весь ваш код CSS должен быть расположен на /app/assets/stylesheets

Я не пробовал это, но похоже, что верно следующее:

  • если у вас есть content_for, который является javascript (например, с настоящим javascript внутри него), sprockets не будет знать об этом, и, следовательно, это будет работать так же, как и сейчас.

  • если вы хотите исключить файл из большого пакета javascript, вы должны зайти в файл config / sprockets.yml и соответственно изменить исходные файлы. Затем вы просто включите любые файлы, которые вы исключили, где это необходимо.

Я объединил некоторые ответы в:

Помощник по применению:

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

макеты /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  

Следуя указаниям Райана, вот что я сделал:

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, например controller: users, action: 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}}

Вот как это сделать, особенно если вам не нужно выполнять тонны библиотек для конкретной страницы, а выполнять более или менее несколько сотен строк JS.

Так как встраивать код Javascript в HTML совершенно нормально, просто создайте его в каталоге app/views shared.js и поместите туда свой код для страниц / страниц в my_cool_partial.html.erb

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

Так что теперь, откуда вы хотите, вы просто делаете:

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

И это все, к?

Другие вопросы по тегам