Scriptorama.nl

Header image showing a keyboard, mouse, laptop and books on design patterns

Edit-in-Place met Rails

Dit keer zullen we met Ruby On Rails een simpele feature tonen. Af en toe heb je velden die je vaak wilt wijzigen en elke keer moet je eerst op de 'edit' knop drukken voordat je daadwerkelijk gegevens kunt wijzigen. Met Rails' scaffolding mogelijkheden lukt dit al aardig. We zullen stap voor stap laten zien hoe we een edit-in-place feature in je applicatie kunt toepassen, zodat bewerkingen via de traditionele scaffolding manier onnodig is.

Setup

We gaan ervan uit dat je RoR succesvol geinstalleerd hebt. Heb je het nog niet draaien? Zoek dan hier je tutorial op hoe je dat moet doen. Goed, open nu twee commandline windows. In de eerste moeten we eerst een nieuw Rails project aanmaken.
$ rails dvd
Als het goed is wordt er een nieuwe map aangemaakt dvd in je huidge directory met allemaal bestanden en mappen.
In je tweede commandline window starten we de webserver op. Ga eerst naar de projectmap dvd toe en type dit:
$ ruby script/server
Deze tweede commandline window gebruiken we verder niet meer, behalve voor het sluiten van de web server. Sluit dit venster niet! Als alles goed is verlopen draait nu een web server die je kunt bereiken op poort 3000. Ga je naar http://localhost:3000/ dan moet je een standaard pagina zien, wat er zo uitziet:
Default

Database en scaffolding

Zorg ervoor dat MySQL server draait en maak een nieuwe database en tabel aan.

CREATE DATABASE dvd_development;
USE dvd_development;
CREATE TABLE `dvds` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`title` VARCHAR( 100 ) NOT NULL ,
`director` VARCHAR( 100 ) NOT NULL ,
`description` TEXT NOT NULL
) TYPE = innodb;

Bij elk Rails project hoor je eigenlijk drie databases aan te maken, een production, test en development. Deze drie databases zou je dus moeten aanmaken:

dvd_development
dvd_test
dvd_production

Wij houden het nu alleen voor development en deze wordt aanvankelijk ook gebruikt.
Het is belangrijk dat je tabel een unique id heeft. Rails gaat ervan uit elk tabel een primary key heeft genaamd id, wat ook een integer moet zijn. Het is aangeraden om bij deze conventie te blijven.

$ cd dvd
$ ruby script/generate scaffold Dvd

We hebben nu een model aangemaakt met scaffold mogelijkheden.

Voor diegene die onbekend zijn met scaffolding, het is een geautomatiseerd framework om bewerkingen door te voeren op een Model. Zoals je waarschijnlijk al weet is RoR gebaseerd op het MVC pattern, M staat voor Model. Een Model representeert een data source, zoals text files, XML files of databases. In dit geval representeert een Model onze database gegevens. Met scaffolding worden automatisch formulieren klaar gemaakt om nieuwe gegevens op te slaan, bestaande gegevens wijzigen of verwijderen. Het is bedoeld om in ontwikkelingsfase snel gegevens te manipuleren.

In Rails is een model naam geassocieerd met een tabelnaam in meervoud. Ons model is dvd, dus onze tabelnaam is dvds. Dit is belangrijk om te weten, zodat je in de toekomst niet deze fout maakt.

Ga nu naar http://localhost:3000/dvds om nieuwe DVD's toe te voegen. Wat je nu ziet is het scaffolding gedeelte. Voeg een paar DVD's toe zodat we met deze gegevens kunnen manipuleren. Een voorbeeld staat hieronder:
Default

Rechts staan links om gegevens te bekijken, bewerken of verwijderen. Om elke keer op 'edit' te klikken om gegevens te bewerken is zo 2000. We leven nu in web 2.0 tijdperk, dus dit kan beter. We gaan gebruik maken van Rails' In-Place feature.

Rails' In-Place editor

Rails heeft een handige feature genaamd in_place_editor_field, wat in feite een wrapper is voor script.aculo.us' inPlaceEditor. Zonder een 'edit' knop kun je direct gegevens bewerken.

Als eerst moeten we de nodige javascript libraries erbij halen. Open app/views/layouts/dvds.rhtml en deze ziet er standaard zo uit:

CODE:
  1. <html>
  2. <head>
  3.   <title>Dvds: <%= controller.action_name %></title>
  4.   <%= stylesheet_link_tag 'scaffold' %>
  5. </head>
  6. <body>
  7.  
  8. <p style="color: green"><%= flash[:notice] %></p>
  9.  
  10. <%= @content_for_layout %>
  11.  
  12. </body>
  13. </html>

Zet tussen de <head> tags dit: <%= javascript_include_tag :defaults %>
We zetten dit nu alleen in het scaffold gedeelte, maar als je deze libraries overal nodig hebt kun je beter een eigen template maken die je overal include.

Nu gaan we het edit-in-place feature toepassen. Open nu app/views/dvds/show.rhtml en deze ziet er zo uit:

RUBY:
  1. <% for column in Dvd.content_columns %>
  2. <p>
  3.   <b><%= column.human_name %>:</b> <%=h @dvd.send(column.name) %>
  4. </p>
  5. <% end %>
  6.  
  7. <%= link_to 'Edit', :action => 'edit', :id => @dvd %> |
  8. <%= link_to 'Back', :action => 'list' %>

De 'Edit' knop kunnen weghalen want we gaan in_place_editor_field van Rails gebruiken. Dit is de nieuwe show.rhtml:

RUBY:
  1. <% for column in Dvd.content_columns %>
  2. <p>
  3.   <b><%= column.human_name %>:</b>
  4.   <%= in_place_editor_field :dvd, column.name, {}, :rows => 1 %>
  5. </p>
  6. <% end %>
  7.  
  8. <%= link_to 'Back', :action => 'list' %>

Als resultaat kun je nu elk veld bewerken zodra je op de tekst klikt, zoals hieronder een voorbeeld staat.
Default

Druk je echter op 'ok' om op te slaan, dan krijg je een error. Deze staat hieronder:
Default

Het omcirkelde gegeven is belangrijk. Rails zoekt namelijk naar de action set_dvd_<kolom_naam>. In dit geval wilden we de titel veranderen, dus zocht Rails naar action set_dvd_title. Deze bestaat niet dus moeten we deze toevoegen in de controller. Open app/controllers/dvds_controller.rb en zoals je ziet bevat deze al vele actions.

We moeten dus action set_dvd_title toevoegen:

RUBY:
  1. def set_dvd_title
  2. # update met ActiveRecord
  3. end;

Wacht eens even, volgens mij past Rails het DRY (Don't Repeat Yourself) principe toe. Anders moeten we voor elk kolomnaam een action schrijven. Dit is slecht, dus dat doen we anders! Hieronder staat de code waarmee je elk kolom van tabel dvds kunt updaten.

RUBY:
  1. Dvd.content_columns.each do |column|
  2.     in_place_edit_for :dvd, column.name
  3.   end

Via het Model halen we alle kolomnamen op en in_place_edit_for zorgt voor de action afhandeling. Mocht je voor een veld een speciale action willen hebben, dan defineer je die gewoon want die overruled in_place_edit_for.
Nu kun je opnieuw proberen gegevens te bewerken en zoals je ziet werkt het nu wel en zie je onmiddelijk het resultaat.

Laatste woorden

Het kan zijn dat je een <textarea> wilt gebruiken voor grote lappen tekst. Je kunt hiervoor de rows optie wijzigen die je bij de vierde parameter hebt gebruikt.
Een nadeel is dat lege velden niet zomaar te wijzigen zijn, ze zijn niet klikbaar. De enige oplossing, voor zover wij weten, is om deze velden standaard waardes mee te geven. Voor de lezers die dit zonder Rails willen realiseren, hier staat een artikel hoe je dit kunt doen met Prototype en PHP.

Wil je de broncode? Deze is hier te downloaden (63 kb).
Mocht je tegen problemen aanlopen, raadpleeg het forum voor hulp!

Reageer ook!

Nice, Tri!

Mag ik overigens Training Day aanraden? Zeer goed geregisseerd! ;-)

@Mathieu: Ben je nou al gecertificeerd babysitter?

Nee, gezakt :(

Voor de jQuery-fans: er is net een Editable plugin bijgekomen: http://jquery.com/blog/2006/07/12/inline-editing-with-jquery/.

Ja Mathieu, zelf promotie mag altijd :-)

De inline edit feature van JQuery vind ik niet zo goed. Je mist namelijk een submit button of cancel button, wat het erg onduidelijk maakt. Ook moet je op enter drukken om te saven en hij slaat ook niet op als je buiten het veld klikt (wat intuitief overkomt). Heeft dus nog wat tweaking nodig.

De Editable-plugin is inderdaad nogal alpha . Een duidelijk versioning systeem ontbreekt (voorlopig?) nog voor jQuery-plugins.

Als je edit-in-place in andere talen dan ruby wil, check dan even http://www.epointment.com/software/eeip

Da's een stukje Prototype script dat onafhankelijk is van wat er serverside gebeurt, dus of je nu php of ruby gebruikt, aan de frontend kun je hier een widgetje mee editen.

Blijft wel even opletten dat met de standaard IPE je een veld leeg op kan laten slaan waardoor je achteraf geen mogelijkheid meer hebt om het veld met de IPE te bewerken. Makkelijkste oplossing:

def set__
... hier je code voor het opslaan/invullen van default waarde bij 0...
end

Vervolgens wel even netjes een RJS view maken om alle velden te updaten.

Leave a comment
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>