Railsowe helpery (ścieżkowe) a Javascript

Obecny trend / dobra praktyka w pisaniu JS, czyli tzw. unobtrusive javascript, separuje kod widoku (szablon erb/haml) od podpinanego kodu javascript. Poza oczywistymi zaletami ma to jedną wadę: nie sposób w kodzie javascript mieć dostęp do zwyczajnie dostępnych w widokach helperów, zwłaszcza ścieżkowych.

Przykład kodu mieszanego, w którym można:

<%= check_box_tag "images[#{i.id}]", "mark", false, :onchange => "new Ajax.Request('#{mark_image_path(i)}', {asynchronous:true, evalScripts:true, method: 'get'}); " %>

W unobtrusive nie da rady, bo jest wykonywany poza kontekstem renderowania widoku:

<%= check_box_tag "images[#{i.id}]", "mark", false %>

function activate_image_checkboxes_for_marking() { $$('#images input[type=checkbox]').each(function(cbox) { cbox.observe('change', function(ev) { new Ajax.Request(I_CO_TERAZ_GOŚCIU, {asynchronous:true, evalScripts:true, method: 'get'}); return false; }); }); }
Zakładając, że nie chcemy na pałę składać ścieżki z poziomu javascriptu (fuj!), możliwe są dwa obejścia tego braku:

  1. Element siostrzany: każdemu elementowi (np. check_boxowi) dorzucamy “siostrzany” hidden_field, z odpowiednim name, id oraz wartością równą stringowi (np. ścieżka z RESTowego helpera), z którego ma skorzystać javascript.
    Podobny patent stosowany jest przez railsy do obejścia problemu z “niewysyłanymi” wartościami niezaznaczonych checkboxów przez przeglądarki.
  2. (mój ulubiony) Ekstra atrybut: HTML i JS/Prototype łykną sporo, także dodanie checkboxowi ekstra atrybutu, z którego sobie skorzystamy w javascripcie:
<%= check_box_tag "images[#{i.id}]", "mark", false, :mark_path => mark_image_path(i) %>

I potem można wygodnie skorzystać z prototype’owego readAttribute:

function activate_image_checkboxes_for_marking() { $$('#images input[type=checkbox]').each(function(cbox) { cbox.observe('change', function(ev) { new Ajax.Request(cbox.readAttribute('mark_path'), {asynchronous:true, evalScripts:true, method: 'get'}); return false; }); }); }

Ta druga opcja jest ok, o ile nie zależy Ci na walidacji HTMLa :slight_smile:

Nie wiem jak dla Prototype, ale jeżeli ktoś używa jQuery, to wyjściem z tej sytuacji jest plugin metadata.

Dla przykładu:

<%= check_box_tag "images[#{i.id}]", "mark", false, :class => "{url: '#{mark_image_path(i)}'}" %>
to powyższe wygeneruje coś takiego:

<input type="checkbox" name="images[1]" value="mark" class="{url: '/images/1/mark'}" />

i później w javascripcie:

jQuery("input[type=checkbox]").livequery("change", function() { $(this).metadata(); // => {url: '/images/1/mark'} });
Dane dla metadata można jeszcze umieszczać w atrybucie data (ale to znowu może skutkować z problemami z walidacją) i między tagami - przykłady są na stronie metadata.

Druga dobra opcja, ale tylko w wypadku urli, to less js routes: http://github.com/stevenbristol/less-js-routes/tree/master
Plugin napisany przez gości od lovd by less. Generuje plik javascript ze wszystkimi routesami, dzięki czemu w javascripcie można ich używać tak samo jak w railsach. I tym samym można napisać mark_image_path(1) w javascripcie, co zwróci prawidłową ścieżkę. Minus jest taki, że id i tak trzeba jakoś przekazać, ale to już jest łatwiejsze, bo z reguły można id obiektu wyciągnąć z atrybutu id w hmlu (dom_id(image)). Bardziej lubię metadata, ale ostatecznie to też ujdzie :wink: I można wygenerować wersję routesów dla prototype.

EPIC! Dzięki za linka! :slight_smile:

Nie podoba mi się pomysł z serializacją danych w atrybucie “name” - to już wolę świadomie złamać walidację (albo podłożyć własny DTD :wink: ) i dodać jeden atrybut o znaczącej nazwie, bez kaleczenia powszechnie znanych i mających konkretne znaczenie.