Wyświetlanie drzewa

Cześć,
Dzisiaj następujące pytanie: w jaki sposób ułatwić sobie wyświetlanie drzewa ( model typu acts_as_tree ) - normalnie działa to tak, że mamy funkcje rekurencyjną która przechodzi przez wszystkie gałązki i je wyświetla.
Ale jestem pewien że w Railsach jest to rozwiązane dużo lepiej, niestety nie wiem jeszcze jak, wiec prosze was o wskazówki :wink:

powiedzmy że mam akcje list i szablon widoku list.rhtml i chce aby drzewo wyświetlało sie prymitywnej formie typu:

[code]Kategoria

  • Podkategoria1
  • -Jeszcze głebiej
  • -Jeszcze głebiej2
  • Cośtam

Kategoria2

  • Tralalala
  • LALALALA
    • Głebiej
      • Jeszcze Głębiej…
    • Głębiej 2[/code]
      ascii-art powinien być jasny :wink:

Railsy Ci dają tylko zbiór metod, którymi łatwo możesz pobrać pewne elementy drzewa - korzeń, dzieci, braci itp.

A czy funkcję napiszesz rekurencyjnie, czy iteracyjnie to już Twoja sprawa. :slight_smile:

Nie wiem czy wyobrażasz sobie jakiś inny algorytm wyświetlający całe drzewo :wink: (pomijając iteracyjną wersję tego algorytmu).

list.rhtml

[code=html]


<%= render :partial => “list_item”, :collection => @items %>
[/code] _list_item.rhtml [code=html]
  • <%= list_item -%>
  • <% if list_item.children_count() > 0 %>
    <%= render :partial => "list_item", :collection => list_item.all_children %>
    <% end %>
[/code] Rozwiązanie teoretyczne :D nie testowałem. Dla każdego elementu items wyświetli sie szablon, który w przypadku posiadania dzieci będzie wywoływał sie rekurencyjne (wydajność?)

Pozdrawiam

ps. sorki za te znaczniki html, ale JAK je wstawić w code, by nie zostały wycięte?
ps2. code=html :smiley:

Ok, zmodyfikowałem trochę to co napisałeś i wg mnie powinno to wyglądać tak:

list.rhtml

[code=html]


    <%= render( :partial => ‘list_item’, :collection => @categories ) %>
[/code] _list_item.rhtml [code=html]
  • <%=h list_item.name %>
  • <% if list_item.children_count() > 0 %>


      <%= render( :partial => ‘list_item’, :collection => list_item.children) %>

    <% end %>[/code]
    Ważne żeby w kontrolerze dać:

    def list @categories = Category.find( :all, :conditions => [ "parent_id = 0"] ); end
    Żeby znalazł nam tylko korzenie.
    Dodatkowo mam pytanie: nie wiem jak zapisac w ruby warunek, żeby wybrać wiersze z polem parent_id = NULL, ani :conditions => [ “parent_id = NULL”] ani :conditions => [ “parent_id = ?”, nil] nie dziala ? dlatego zamienilem NULL na 0 w tym wypadku.

    [quote=balinski]def list @categories = Category.find( :all, :conditions => [ "parent_id = 0"] ); end
    [/quote]
    Prawidłowo powinno być

    def list @categories = Category.find( :all, :conditions => [ "parent_id is null"] ); end
    Tak masz racje, zmiany były niezbędne… późno było :smiley:

    Możesz tak:

    :conditions => ‘parent_id IS NULL’

    albo tak:

    :conditions => ['parent_id IS ?, nil]

    Pierwszy sposób lepszy bo krótszy :slight_smile:

    [quote=hosiawak]:conditions => ‘parent_id IS NULL’

    albo tak:

    :conditions => ['parent_id IS ?, nil]

    Pierwszy sposób lepszy bo krótszy :)[/quote]
    Drugi sposób wspaniale nadaje się do wykorzystania w sytuacji gdzie nil jest wartością parametru. Jest to bardziej czytelny. Choć oczywiście nie w tym konkretnym przypadku, bo tu, jeśli dobrze rozumiem, param permanentnie równa się nil.

    param = nil :conditions => ['parent_id IS ?, param]

    Albo jeszcze fajniej:

    Category.find(:all, :conditions => {:parent_id => nil })

    :slight_smile:

    większość algorytmów do przechodzenia grafów to alg. rekurencyjnie, stosuje się inne rozwiązania do naprawdę zaawansowanych przypadków, ale i tam wykorzystuje się rekurencje…

    dla
    categories_helper.rb

    [code=ruby]module CategoriesHelper

    def make_tree(family)
    if family.parent_id.nil?
    str = ‘


      str += ‘
    • ’ + family.name + ‘

    • str += find_child(family)
      str += ‘

    end
    str
    end

    def find_child(parent)
    str = ‘


      if parent.has_children?
      parent.children.map do |child|
      if child.has_children?
      str += ‘
    • ’ + child.name + ‘

    • str += find_child(child)
      else
      str += ‘
    • ’ + child.name + ‘

    • end
      end
      end
      str += ‘

    end

    end[/code]
    categories_controller.rb

    @all = Category.find(:all, :conditions => {:parent_id => nil})

    list.haml

    - for cat in @all - if cat.parent_id.nil? = make_tree(cat)
    do elementu formularzy collection_select warto dodać

    {:include_blank => true}

    aby móc tworzyć korzenie

    Odgrzebuję stary temat bo mam problem z implementacją. Zrobiłem wszystko z waszymi wskazówkami, wszystko jest funkcja list wyświetla mi drzewo kategorii. Lecz teraz mam problem, jak należy wywołać z poziomu layoutu tą funkcję list żeby kategorie były wyświetlane przy jego użyciu?

    Probowałem poprzez

    <%= render :partial => “list” %>

    lecz otrzymuję błąd o pustym obiekcie. Jak to najlepiej ominąć? Wiem że da się to zrobić poprzez zmianę <%= render( :partial => ‘list_item’, :collection => @categories ) %> na <%= render( :partial => ‘list_item’, :collection =>Category.find_all_by_parent_id(nil) ) %> ale nie jest to chyba poprawne z punktu widzenia programistycznego. Jak to powinno się wykonać najlepiej?

    Pozdrawiam

    Jesteś przekonany, że

    <%= render( :partial => 'list_item', :collection => Category.find_all_by_parent_id(nil) ) %>

    działa, a

    <%= render( :partial => 'list_item', :collection => @categories ) %>

    nie?

    Przecież to to samo (zakładając, że w kontrolerze ustawiasz @categories).

    Ja mam tak:

    category.rb

    named_scope :top_level, :order => :name, :conditions => { :parent_id => nil }

    kontroler:

    @categories = Category.top_level

    application_helper.rb

    [code] # Generuje listę

      dla podanej kolekcji +collection_tree+.

      Elementy w kolekcji muszą się +actować_as_tree+.

      Metoda rekurencyjna - tworzy wszystkie podpoziomy w jednym wywołaniu.

      def tree_ul(collection_tree, &block)
      if collection_tree.size > 0
      ret = ‘


        collection_tree.each do |item|
        ret += ‘

      • ret += yield item
        ret += tree_ul(item.children, &block) if item.children.size > 0
        ret += ‘

      • end
        ret += ‘

      end
      end[/code]
      widok:

      <%- cache do -%> <%= tree_ul(@categories) do |category| content_tag :span, category.name, :class => "folder" end %> <%- end -%>