dimanche 21 juin 2009

Base d'un wiki avec éditeur wysiwyg, gestionnaire de version et pagination


Troisième partie avec l’intégration rapide de la pagination (will_paginate), la mise en place d’un simili wiki (les wikiwords ne sont pas encore mis en place) avec un éditeur WYSIWYG : tinyMCE, complété par un gestionnaire de version et une solution de remplacement : Textile.

Prochaine partie : mise en place des commentaires avec acts_as_comment.




1. Préparation
Téléchargez le plugin de Kete, décompressez le dans le répertoire vendor/plugin/tiny_mce et à la racine du projet :
>rake tiny_mce :install

Tous les fichiers de la version actuelle (3.2.4.1) seront mis en place.

Pour Will_paginate, installez la gem :
>gem install mislav-will_paginate

Et déclarez-là dans le fichier environment.rb
Rails::Initializer.run do |config|
config.gem "authlogic"
config.gem "justinfrench-formtastic", :lib =>'formtastic', :source =>'http://gems.github.com'
config.gem 'mislav-will_paginate',  :lib => 'will_paginate', :source => 'http://gems.github.com'

Pour act_as_versioned, l’installation de la gem ne fonctionne pas ( ??), donc on télécharge le plugin et on l’installe directement sous vendor/plugin.
Enfin, même procédé pour acts_as_textiled : sous vendor/plugin.

2. Wiki
Un simple modèle suffira pour l’instant, avec une différenciation créateur/éditeur de la page :
>ruby script/generate scaffold wiki title :string page:text owner_id:integer writer_id:integer

Modification du modèle wiki :
class Wiki < ActiveRecord::Base
belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
belongs_to :writer, :class_name => "User", :foreign_key => "writer_id"  
end

Et du modèle User (j’en profite pour créer un champ virtual pour facilité la lecture des noms/prénoms) :
class User < ActiveRecord::Base

acts_as_authentic do |c|
c.login_field = :ident
end

has_many :wiki_owner, :class_name => "Wiki", :foreign_key => "owner_id"
has_many :wiki_writer, :class_name => "Wiki", :foreign_key => "writer_id"

def full_name
n = self.lastname.nil? ? "" : self.lastname.split('-').map{|x| x.first.capitalize}.join('.')
n + ". " + self.username.capitalize
end

end

Le controller du wiki ne bouge pas trop, sauf les mise à jours des champs owner/writer sur la creation et l’update (à noter la restriction before_filter et la préparation des wikiword) :
class WikisController < ApplicationController
CamelCase = Regexp.new( '\b((?:[A-Z]\w+){2,})' )
before_filter :require_user

# GET /wikis
def index
@wikis = Wiki.all
end

# GET /wikis/1
def show
@wiki = Wiki.find(params[:id])
if params[:version_id]
@wiki.revert_to params[:version_id]
end
end

# GET /wikis/new
def new
@wiki = Wiki.new
end

# GET /wikis/1/edit
def edit
@wiki = Wiki.find(params[:id])
end

# POST /wikis
def create
@wiki = Wiki.new(params[:wiki])
@wiki.owner = current_user
@wiki.writer = current_user
if @wiki.save
flash[:notice] = 'Wiki was successfully created.'
redirect_to @wiki
else
render :action => "new"
end
end

# PUT /wikis/1
def update
@wiki = Wiki.find(params[:id])
@wiki.writer = current_user
if @wiki.update_attributes(params[:wiki])
flash[:notice] = 'Wiki was successfully updated.'
redirect_to @wiki
else
render :action => "edit" 
end
end

# DELETE /wikis/1
def destroy
@wiki = Wiki.find(params[:id])
@wiki.destroy
redirect_to wikis_url
end
end

Le modèle est fonctionnel et vous pouvez le tester sur le site ou dans la console. Avant de modifier les vue, intégrons la partie versionning du wiki.

3. Versionning
Le versionning est très simple à mettre en place :
>ruby script/generate migration WikiVersions

Puis modification du fichier de migration généré :
class CreateWikiVersions < ActiveRecord::Migration
def self.up
Wiki.create_versioned_table
end

def self.down
Wiki.drop_versioned_table
end
end
N’oubliez pas le rake db:migrate. Intégration dans le modèle Wiki app/models/wiki.rb:
class Wiki < ActiveRecord::Base
belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
belongs_to :writer, :class_name => "User", :foreign_key => "writer_id"

acts_as_versioned :if_changed => [:title, :page]  
def writers
self.versions.map(&:writer_id).uniq
end
end
La fonction writers servira à afficher la liste des rédacteurs. Ajout d’une function permettant de changer de version dans app/controllers/wikis_controller.rb qu’on utilisera dans les vues :
def revert_to_version
@wiki = Wiki.find(params[:id])
@wiki.revert_to!(params[:version_id])
redirect_to @wiki
end
4. Les vues Copier coller des vues, adoptant la même structure que les vues User : app/views/wikis/index.html.haml :
%h1 Wikis
%h3 Listing Wikis
%table.table_index
%thead
%tr
%th Title
%th Owner
%th Writer
%th Version
%th 
%tbody   
- @wikis.each do |w|
%tr{ :class => cycle('even', 'odd')}
%td= w.title
%td= w.owner.full_name
%td= w.writer.full_name
%td= w.version
%td
= link_to 'Show', w
= link_to 'Edit', edit_wiki_path(w)
= link_to 'Destroy', w, :confirm => 'Are you sure?', :method => :delete
%br
= link_to 'New wiki', new_wiki_path
app/views/wikis/new.html.haml :
%h1 Wiki
%h3 New Wiki
#bloc_form
= render @wiki
%br
= link_to 'Back', wikis_path 
app/views/wikis/edit.html.haml :
%h1 Wiki
%h3 Edit Wiki
#bloc_form
= render @wiki
%br
= link_to 'Back', wikis_path
app/views/wikis/_wiki.html.haml :
- semantic_form_for @wiki do |f| 
= f.error_messages
- f.inputs do
= f.input :title
= f.input :page
- f.buttons do
= f.commit_button
app/views/wikis/show.html.haml :
%h1 Wiki
%h3= @wiki.title
#wiki_page
=@wiki.page
#wiki_foot 
Owner
= link_to @wiki.owner.full_name, user_path(@wiki.owner)
Writer
- User.find(@wiki.writers).each do |w|
= link_to w.full_name, user_path(w)
%br
Versions
- for v in @wiki.versions.reverse
= "["+ v.version.to_s + ":"
= link_to 'show', wiki_path(@wiki, :version_id => v.version)
= link_to 'revert', :action => 'revert_to_version', :version_id => v.version, :id => @wiki
= "]"
%br
= link_to 'Edit', edit_wiki_path(@wiki)
|
= link_to 'Back', wikis_path 
Et un petite modif pour le css du pied de page wiki (à rajouter à la fin de public/stylesheets/sass/red.sass ) :
// ------------------ wiki
#wiki_foot 
:background-color #FEC
:border 2px solid #DBCCB6
:margin-top 120px
:padding 12px
Voilà le gestionnaire de version est maintenant fonctionel. Reste à intégrer acts_as_textiled, tinyMCE, et will_paginate. 5. Acts_as_textiled Textile est un format d’édition rapide (voir Redcloth). Le plugin acts_as_textiled permet de l’utiliser rapidement sur toutes les vues (dans les champs string et textarea) sans les modifier. Il suffit de modifier le modèle comme suit : app/models/wiki.rb
class Wiki < ActiveRecord::Base
belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
belongs_to :writer, :class_name => "User", :foreign_key => "writer_id"  

acts_as_versioned :if_changed => [:title, :page]  
acts_as_textiled :page

def writers
self.versions.map(&:writer_id).uniq
end
end
Acts_as_textiled peut ainsi être rapidement implentés dans tous vos futures modèles. 6. Will_paginate Pour la pagination, rien de plus simple là aussi après avoir installé le plugin ou la gem. Un exemple complet se trouve sur RailsCast épisode 51. Pour les mettre sur la vue users, on modifie d’abord le controller app/controllers/users_controler.rb, la méthode index :
def index
@users = User.paginate :page => (params[:page]||1), :order => 'username ASC', :per_page => 10
respond_to do |format|
format.html # index.html.erb
format.xml  { render :xml => @users }
end
end
et on rajoute en fin de fichier de la vue app/views/users/index.html.haml :
%br
= will_paginate @users
= link_to 'New user', new_user_path 
Même chose pour le wiki :
#dans le controller wikis_controller.rb, methode index
def index
@wikis = Wiki.all
end
# devient
def index
@wikis = Wiki.paginate :page => (params[:page]||1), :order => 'title ASC', :per_page => 10
end
# et on rajoute dans le fichier app/views/wikis/index.html.haml la ligne :
= will_paginate @wikis
Reste une petite modification rapide du css (toujours public/stylesheets/sass/red.sass) :
// ------------------ pagination  
.pagination
:padding-top 20px
//:text-align center
a
:padding 2px
:border 2px solid #DBCCB6
:font-weight normal
:background-color #FEC
:color #000
:text-decoration none
&:hover, &:active
:color #FFF
:background-color #A00
span
&.current, &.disabled
:padding 2px
:border 1px solid #000
:background-color #FFF
:color #000
7. TinyMCE Le plugin correctement installé (voir partie 1 au début), il reste à l’intégrer : Modifiez le layout principal pour prendre en compte les fichiers de TinyMCE (dans app/views/layout/application.html.haml) en rajoutant la ligne dans l’en-tête = include_tiny_mce_if_needed :
!!!
%html
%head
%title Red
= stylesheet_link_tag 'scaffold'
= stylesheet_link_tag 'formtastic'
= stylesheet_link_tag 'red'
= stylesheet_link_tag 'login' if not current_user
= javascript_include_tag :defaults
= include_tiny_mce_if_needed
%body
= render :partial => 'shared/head' if current_user
%p.flash_notice= flash[:notice]
#content= yield
Puis dans le controlleur qui l’utilise (app/controllers/wikis_controller.rb) rajoutez uses_tiny_mce :
class WikisController < ApplicationController
CamelCase = Regexp.new( '\b((?:[A-Z]\w+){2,})' )
before_filter :require_user
uses_tiny_mce

# GET /wikis
def index
...
Et enfin de modifier la vue en changeant juste la classe du textarea :
- semantic_form_for @wiki do |f| 
= f.error_messages
- f.inputs do
= f.input :title
= f.input :page, :input_html => { :class => 'mceEditor', :style => 'width:800px' }
- f.buttons do
= f.commit_button
Et ça fonctionne :) vous pouvez utiliser aussi du code textile à l’intérieur, il sera correctement mis en valeur. Pour mettre quelques options standard (voir la doc sur le site de TinyMCE), on peut faire par exemple (dans le controller du wiki) :
uses_tiny_mce :only => [:new, :create, :edit, :update], :options => {
:theme => 'advanced',
:theme_advanced_resizing => true,
:theme_advanced_resize_horizontal => false,
:plugins => %w{table fullscreen contextmenu},
:theme_advanced_toolbar_align => 'left',
:theme_advanced_toolbar_location => 'top',
:theme_advanced_buttons1 => 'undo,redo,cut,copy,paste,pastetext,|,bold,italic,strikethrough,blockquote,charmap,bullist,numlist,removeformat,|,link,unlink,image,|,cleanup,code',
:theme_advanced_buttons2 => 'formatselect,fontselect,fontsizeselect,|,justifyleft,justifycenter,justifyright,indent,outdent,|,forecolor,backcolor,|,table,fullscreen',
:theme_advanced_buttons3 => ''
}
TODO - speelchecking pour TinyMCE, - ajax sur les liens de paginations will_paginate, - et wiki complet (wikiword, pages spéciales, etc ...)

4 commentaires:

  1. Merci beaucoup pour ce blog, il arrive à point nommé !
    Mais, vis-à-vis de cette article, je pense qu'il serait meilleur avec une démonstration des alternatives comme Markdown.

    RépondreSupprimer
  2. Et aussi quelque chose de plus léger (et pas WYSIYG) que TinyMCE

    RépondreSupprimer
  3. Hello Rhyhann, c'est noté pour les suggestions et dans l'article sur la mise en place complète du wiki, je parlerai des librairies redcloth/bluecloth/wikitext pour l'édition (et d'autres si j'en trouve).

    RépondreSupprimer
  4. Merci pour les billets, ils sont clairs et intéressants !

    RépondreSupprimer