Coffe script nie działa?

Niby wszystko powinno działać … ale nie działa. Pewnie to jakiś prosty błąd ale CS i JS w RoR to dla mnie nowość.

static_pages.coffee

$('#button').click ->
    $('#object').slideToggle("slow")

application.js

//= require jquery
//= require bootstrap-sprockets
//= require jquery_ujs
//= require turbolinks
//= require jquery.turbolinks
//= require_tree .

i fragment strony;

<p id="button">ALibaba</p>
<div class="box" id="button"></div>
<div class="box" id="object"></div>

Oraz CSS

.box {
    border: solid black 1px;
    width: 50px;
    height: 50px;
}
#object {
    display: none;
}

Strona wyświetla się w ramach kontrolera static_pages_controller.

Prosta sprawa po kliknięciu w napis lub kwadrat powinien pokazac sie drugi kwadrat. Z tego co czytałem static_pages.coffee powinien automatycznie się załadować i działać … Ktoś pomoże mi rozwiązać ten problem.

O tym czy plik się ładuje czy nie przekonasz się łatwo wpisując w pierwszej linii static_pages,coffee jakiegoś console.log czy alert.
Na pewno bindujesz się do clicka gdy element #button jest JUŻ w html a nie ZANIM do niego trafi?

Załaduj stronę i w konsoli JS wpisz $('#button').click(function(e) { $("#object").slideToggle('slow'); } ) i wtedy kliknij w tego buttona.

Tak BTW - static_pages.js.coffee

Może idę w złą stronę, ale to może być wina turbolinksów też. One bardzo nie lubią jak się używa zdarzeń na obiektach wprost, zazwyczaj lepszym rozwiązaniem jest delegowanie ich przez document:

$(document).on('click', '#button', function() {
    $('#object').slideToggle('slow');
});

Czyli w tłumaczeniu na Coffee:

$(document).on 'click', '#button', ->
  $('#object').slideToggle 'slow'

Takie zdelegowanie akcji powinno rozwiązać problem tego czy element jest, czy go nie ma w momencie ładowania JSa, co więcej będzie on dalej działał kiedy body zostanie podmienione przez Turbolinks.

1 Like

I punkt dla paweljw działa nie rozumiem jeszcze o co tu chodzi ale działa !!! więc jest dobrze. Mógłbyś jakoś rozwinąć może na czym polega różnica ??? Bo chciałbym to lepiej zrozumieć :smile:

Zapis

$(document).ready(

w CoffeeScripcie to po prostu

 $ ->
     console.log('dom ready')

Jeśli znasz konstrukcje JSa ale masz problem z ich przetłumaczeniem na CoffeeScript, polecam to jeśli jeszcze nie czytałeś: https://css-tricks.com/jquery-coffeescript/ szybka referencja jak przełożyć typowe wyrażenia w JS + jQuery na Coffee.

Edit: spieszę wyjaśnić :smile:

Kiedy normalnie przeładowuje Ci się strona w przeglądarce, leci zdarzenie ready na document. Wszystkie skrypty z body są wykonywane i jest cacy, a zdarzenia click możemy podpinać w document:ready, bo wtedy już te elementy do których je podpinamy istnieją.

Jajko kwadratowe się zaczyna jak mamy Turbolinks, bo one nie przeładowują strony per se. One robią w tle zapytanie AJAXowe udające “kliknięcie w link”, po czym podmieniają body strony na to z odpowiedzi serwera (podmieniają też parę innych rzeczy - między innymi tag title). Wtedy zdarzenie document:ready nie wypala. W konsekwencji wszystko co mięliśmy podpięte przez np takie zwykłe click też nie wypala, bo element nie istnieje.

Kiedy pisze się z tą delegacją przez body, nie zakładamy listenera na obiekt który może nam zeżreć (po przeładowaniu turbolinksami na przykład), tylko listener leci na samo body, które nam nie zniknie. Dopiero body szuka “w sobie” selektora zgodnego z drugim parametrem do funkcji .on() i wykonuje w jego kontekście funkcję podaną jako trzeci parametr.

Tak jeszcze na marginesie, turbolinks generują zdarzenie po “przeładowaniu” nimi strony, ale nie jest to document ready. Możesz skorzystać z wydarzenia

$(document).on('page:load', function() {...});

Nic nie zrozumiałem, ale czuję się mądrzejszy :D, widać muszę bliżej przyjrzeć się tematowi i wrócić do Twojej odpowiedzi, za którą ślicznie dziękuję :slight_smile:

Spróbuj się też przegryźć przez dokumentację turbolinks, a przynajmniej przez sekcję Events: https://github.com/rails/turbolinks Turbolinksy to patent z potencjałem, ale normalnemu użytkowaniu jQuery wsadza kij w szprychy. Trzeba pokochać albo wyrzucić z projektu, jedno z dwóch :slight_smile:

Może ja spróbuję własnego tłumaczenia.
Standardowe:

$('#button').click ->
    $('#object').slideToggle("slow")

Przypisuje zdarzenie do elementu strony. Nie działa dla turbolinków i Ajaxa bo elementu do którego chciałbyś przypisać zdarzenie nie ma jeszcze na stronie w momencie wykonywania skryptu.

Dla turbolinków (i w ogóle elementów ładowanych Ajaxem):

$(document).on 'click', '#button', ->
  $('#object').slideToggle 'slow'

Zdarzenie jest przypisane do dokumentu (który zawsze jest obecny). Po wyłapaniu zdarzenia sprawdzane jest którego obiektu bezpośrednio dotyczyło (tutaj ‘#button’).

Sugerowałbym zacząć od zapoznania się z jQuery, bo to raczej tutaj tkwi problem w zrozumieniu. Turbolinki to zwykły Ajax, tyle że sprytnie wykorzystany :slight_smile:

1 Like

Ok chce pójść dalej i wykorzystać tę wiedzę, problem wygląda tak: na stronie generowane są div-y o id “show_comment” które mają css display:none, potem “comment_create” też display: none, a następnie dodawane są przyciski “show_comment_button” i “create_comment_button”, całość jest w div-ie “announcement”, chcę napisać skrypt który po naciśnięciu danego przycisku spowoduje pokazanie się tylko poprzedzającego go odpowiedniego diva.

Moja próba:

$(document).on 'click', '#show_comment_button', ->
  this.prev('#show_comment').slideToggle 'slow'

$(document).on 'click', '#create_comment_button', ->
  this.prev('#create_comment').slideToggle 'slow'

Oczywiście nie działa :frowning:

Po pierwsze, jeśli masz czegoś więcej niż jedno, to nie operuj id, tylko klasami, bo inaczej jQuery się popierniczy o który element Ci chodzi (standard mówi, że ID jest unikalnym identyfikatorem danego elementu w jego “domowym poddrzewie” - jakkolwiek dziwnie to nie brzmi, w tłumaczeniu na ludzki chodzi o unikalność w obrębie aktualnie renderowanego HTMLa).

Druga sprawa - prev() wybiera Ci tylko natychmiastowo najbliższy poprzedni element z rodzeństwa aktualnego elementu, więc mając

<div class="jeden"></div>
<div class="dwa"></div>
<button class="trzy">...</button>

Używając $('.trzy').prev() możemy się dostać tylko i wyłącznie do diva o klasie “dwa”, nie do jeden. Rozwiązaniem tego problemu jest prevAll(), która zwraca całe poprzedzające dany element rodzeństwo. Oczywiście w naszym przypadku nie chcemy wszystkich poprzedzających elementów, tylko konkretnie najbliższego diva o danej klasie, więc na przykładzie powyżej, żeby dostać się do diva o klasie jeden moglibyśmy napisać $('.trzy').prevAll('div.jeden:first') - to nam daje gwarancję że zawsze będzie to najbliższy div o danej klasie.

Tu masz demo jak mógłbyś użyć tego w swoim przypadku: http://jsfiddle.net/vkrqehwt/. Przepisanie tego na CoffeeScript pozostawiam jako tak zwane łatwe ćwiczenie dla czytelnika :stuck_out_tongue:

Zadanie odrobione :slight_smile: świetna sprawa, w mojej nauce Raisów często pomijałem JS, bo bez CSS strony nie będzie a bez JS jakoś to będzie, teraz wychodzą moje braki na tym polu… nic trza ponadrabiać braki będzie.

Dzięki wielkie :slight_smile:

Ehh dukam już od godziny i mam kłopot. Zachciało się dodać licznik do długości postów więc mój plan wygląda tak:

$(document).on ‘keyup’, ‘.content_length’, ->
$(this).nextAll(‘div.char_counter:first’).text(255 - $(this).val().length);

Ale nie działa. jak usunę ‘(this).nextAll’ wszystko śmiga tyle że zmienia wartość wszystkich div-ów

Licznik umieściłem w tak:

<div class="create_comment" >
  <%= form_for(announcement.comments.create, url: announcement_comments_path(announcement.id) ) do |form| %>
    <%= form.label :author, "Autor:" %>
    <%= form.text_field :author, class: "form-control" %>

    <%= form.label :content, "Treść:" %>
    <%= form.text_field :content, class: "form-control content_length" %>
    <div class="char_counter">255</div>
    <%= form.submit "Dodaj!", class: "btn btn-primary" %>
  <% end %>
</div>

Cześć,
Na początek może doradzę Ci żeby funkcję inicjującą umieszczać w
$(document).ready(funkcja)
$(document).on(‘page:load’, funkcja)

Co do twojego licznika to można zrobić tak

$(document).ready ->
  $('.content_length').keydown ->
    len = $(this).attr('value').length
    if len >= 500
      val.value = val.value.substring(0, 500)
    else
      $('.char_counter').text 500 - len
    return
  return

PZDR!

Rozwiązanie mojego wcześniejszego problemu:

$(document).on 'keyup', '.content_length', ->
  counter = $(this).parent().nextAll('div.char_counter:first')
  char_number = (255 - $(this).val().length)
  counter.text(char_number)     
  counter.css('color', if (char_number < 0) then 'red' else 'black')

jeszcze jedno zamien kolejności w application js

  1. //= require jquery
  2. //= require jquery_ujs
  3. //= require jquery.turbolinks
  4. //= require bootstrap-sprockets
  5. //= require turbolinks
  6. //= require_tree