Tag: plugin

Fckeditor, Rails oraz toolbarStartExpanded czyli autorozwijanie paska narzędzi

Korzystając z FCKEditora, mamy możliwość zdecydowania w pliku fckconfig.js czy pasek narzędziowy ma być domyślnie rozwinięty czy też nie.

Jednak w wielu przypadkach, jest to za mało. Często nie chcemy w całej aplikacji definiować jednego "stylu", a zależnie od tego gdzie jesteśmy, mieć rozwinięty bądź nie, ten pasek narzędziowy.

Przykładem tego może być prosty CMS, gdzie mamy miejsce edycji tekstów - w takim miejscu ten pasek powinien być rozwinięty. Jednak mamy też np miejsce wpisywania opisów do galerii (pomijając fakt że tam wystarczyłoby zwykłe textarea) - w takim miejscu użytkownicy zazwyczaj wpisują tekst, nie "zdobiąc" go zbytnio.

Do Railsów jest plugin umożliwiający wykorzystywanie FCKEditora, dodając wygodny helper fckeditor_textarea. Rozwiązanie to ma jednak jedną zasadniczą wadę: nie można sterować z jego poziomu parametrem: toolbarStartExpanded.

Pokusiłem się i napisałem łatkę, a raczej wersję poprawioną metody fckeditor_textarea. W pliku fckeditor.rb pluginu, znajdujemy wyżej wymienioną metodę i dopisujemy od linijki 30:

      if options[:toolbarStartExpanded].nil?
        toolbar_expand = ''
      else
        toolbar_expand = "oFCKeditor.Config['ToolbarStartExpanded'] = #{options[:toolbarStartExpanded].to_s}"
      end

A następnie w javascript_tag zwracanym, jako trzeci wiersz:

"#{toolbar_expand}\n"<<

i tyle :)

Od teraz możemy korzystać z tego w następujący sposób:

  <%= fckeditor_textarea(:text, :content,
    :toolbarSet => 'Text',
    :toolbarStartExpanded => false,
    :width => '75%',
    :height => '250px',
    :cols => '50',
    :rows=> '8',
    :id => 'fckedit') %>

Poniżej cała metoda fckeditor_textarea:

    def fckeditor_textarea(object, field, options = {})
      var = instance_variable_get("@#{object}")
      if var
        value = var.send(field.to_sym)
        value = value.nil? ? "" : value
      else
        value = ""
        klass = "#{object}".camelcase.constantize
        instance_variable_set("@#{object}", eval("#{klass}.new()"))
      end
      id = fckeditor_element_id(object, field)

      cols = options[:cols].nil? ? '' : "cols='"+options[:cols]+"'"
      rows = options[:rows].nil? ? '' : "rows='"+options[:rows]+"'"

      width = options[:width].nil? ? '100%' : options[:width]
      height = options[:height].nil? ? '100%' : options[:height]

      if options[:toolbarStartExpanded].nil?
        toolbar_expand = ''
      else
        toolbar_expand = "oFCKeditor.Config['ToolbarStartExpanded'] = #{options[:toolbarStartExpanded].to_s}"
      end

      toolbarSet = options[:toolbarSet].nil? ? 'Default' : options[:toolbarSet]

      id = options[:id].nil? ? id : options[:id]
      value = options[:value].nil? ? value : options[:value]

      if options[:ajax]
        inputs = "<input type='hidden' id='#{id}_hidden' name='#{object}[#{field}]'>\n" <<
                 "<textarea id='#{id}' #{cols} #{rows} name='#{id}'>#{value}</textarea>\n"
      else
        inputs = "<textarea id='#{id}' #{cols} #{rows} name='#{object}[#{field}]'>#{value}</textarea>\n"
      end

      js_path = "/javascripts"
      base_path = "#{js_path}/fckeditor/"
      return inputs <<
        javascript_tag("try{var oFCKeditor = new FCKeditor('#{id}', '#{width}', '#{height}', '#{toolbarSet}');\n" <<
                       "oFCKeditor.BasePath = \"#{base_path}\"\n" <<
                       "#{toolbar_expand}\n"<<
                       "oFCKeditor.Config['CustomConfigurationsPath'] = '#{js_path}/fckcustom.js';\n" <<
                       "oFCKeditor.ReplaceTextarea();\n}catch(e){}")
    end

Archiwizacja modelu czyli moje własne Acts as Archivable

Wczoraj pisząc projekt, wpadłem na pomysł wersjonowania tekstów. Postanowiłem że każda zmiana zawartości musi być równoznaczna ze stworzeniem kopii zapasowej. Funkcjonalność tą postanowiłem wydzielić do pluginu, z racji tego że może przydać się w innych modelach.

Jak to zrealizowałem? Postanowiłem skorzystać z pluginu acts_as_tree. Implikuje to niestety to, że jeśli chcemy skorzystać ze struktury drzewa i backupu to niestety musimy poszukać innego pluginu ;)

Struktura całego mechanizmu ma następującą postać:

Tekst (czy też inny obiekt)

  1. Kopia 1
  2. Kopia 2
  3. Kopia 3
  4. etc

Kopie wykonywane są w momencie aktualizacji modelu. Wykorzystany jest do tego callback before_update.

Interfejs pluginu składa się z trzech metod instancyjnych i jednej klasowej. Metody instancyjne to:

  • backup! - Robi kopie samego siebie do archiwum
  • restore!(id) - przywraca kopię o wybranym id lub też jesli wywołane na kopii to przywraca kopię z której wywołaliśmy
  • cleanup! - usuwa wszystkie kopie poza najnowszą

Metoda klasowa:

  • cleanup! - usuwa wszystkie kopie ze wszystkich elementów, pozostawiając tylko po 1 na element

Problemem na jaki natrafiłem, przy tworzeniu tego pluginu, był Paperclip. Wywołując metodę clone lub po prostu:

target.update_attributes(source.attributes)

Obiekt paperclipa nie było kopiowany. Aby to obejść zastosowałem inne rozwiązanie które wykrywa czy dany obiekt posiada w bazie elementy charakterystyczne dla paperclipa, czyli:

  1. _content_type
  2. _file_size
  3. _file_name

Na podstawie przedrostka rozpoznaje jak te obiekty się nazywają, następnie wywołując dla nich metodę przypisania:

        paperclip_obj.each { |obj|
          sym = "#{obj}=".to_sym
          target.send(sym, self.send(obj.to_s))
        }

Dzięki czemu, kopiowane są wszystkie atrybuty oraz obiekty paperclipa (niezależnie od ich ilości). Dzięki czemu archiwizujemy nie tylko sam przykładowo tekst, ale i powiedzmy miniaturkę. Oczywiście ma to swoje minusy - ilość miejsca zajmowanego przez kopie rośnie bardzo szybko. Na szczęście jest wyżej wspomniana metoda cleanup!

A oto jak zastosować plugin:
Przykładowa klasa:

class CoolClass < ActiveRecord::Base
    acts_as_tree
    acts_as_archivable
end

Następnie zrobienie 10 kopii w odstępach 1sekundowych:

    model = CoolClass.create
    10.times {
      model.content += "a"
      sleep(1)
      model.save
    }

I już mamy nasz model + kopię zapasową każdej wersji.

Teraz przywróćmy powiedzmy drugą wersję (czyli tą "prawie" najdawniejszą):

model.children.second.restore!
# Musimy przeładować model bo zmieniają się jego parametry
model.reload

Z racji tego że zmieniają się parametry obiektu w bazie, musieliśmy wykonać przeładowanie.

Przywrócenie można zrobić także na drugi sposób, wywołując metodę restore! na obiekcie w stosunku do którego chcemy wykonać przywrócenie:

model.restore(model.children.second.id)

Jeśli tak wywołamy przywracanie, nie musimy wywoływać metody reload, ponieważ wywoływana jest automatycznie z poziomu metody restore!

Aby wyczyścić kopie zapasowe, pozostawiając tylko najnowszą, wykonamy:

model.cleanup!

Lub też jeśli chcemy posprzątać wszystkie modele:

CoolClass.cleanup!

Tyle :) aby wyświetlić kopie zapasowe, wystarczy przeiterować dzieci w taki sposób:

model.children.each {
# Jakiś kod :)
}

Plugin do pobrania tutaj: acts_as_archivable

Copyright © 2024 Closer to Code

Theme by Anders NorenUp ↑