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:

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:

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:
-
<html>
-
<head>
-
<title>Dvds: <%= controller.action_name %></title>
-
<%= stylesheet_link_tag 'scaffold' %>
-
</head>
-
<body>
-
-
<p style="color: green"><%= flash[:notice] %></p>
-
-
<%= @content_for_layout %>
-
-
</body>
-
</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:
-
<% for column in Dvd.content_columns %>
-
<p>
-
<b><%= column.human_name %>:</b> <%=h @dvd.send(column.name) %>
-
</p>
-
<% end %>
-
-
<%= link_to 'Edit', :action => 'edit', :id => @dvd %> |
-
<%= 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:
-
<% for column in Dvd.content_columns %>
-
<p>
-
<b><%= column.human_name %>:</b>
-
<%= in_place_editor_field :dvd, column.name, {}, :rows => 1 %>
-
</p>
-
<% end %>
-
-
<%= link_to 'Back', :action => 'list' %>
Als resultaat kun je nu elk veld bewerken zodra je op de tekst klikt, zoals hieronder een voorbeeld staat.

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

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:
-
def set_dvd_title
-
# update met ActiveRecord
-
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.
-
Dvd.content_columns.each do |column|
-
in_place_edit_for :dvd, column.name
-
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!
Volg Scriptorama via RSS!
Reageer ook!
Nice, Tri!
Mag ik overigens Training Day aanraden? Zeer goed geregisseerd! ;-)
Door Mathieu Kooiman
op 07.14.06 @ 12:16 pm | Permalink
@Mathieu: Ben je nou al gecertificeerd babysitter?
Door berry__
op 07.14.06 @ 12:26 pm | Permalink
Nee, gezakt :(
Door Mathieu Kooiman
op 07.14.06 @ 12:39 pm | Permalink
Voor de jQuery-fans: er is net een Editable plugin bijgekomen: http://jquery.com/blog/2006/07/12/inline-editing-with-jquery/.
Door Michel
op 07.14.06 @ 12:58 pm | Permalink
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.
Door Tri Pham
op 07.14.06 @ 1:09 pm | Permalink
De Editable-plugin is inderdaad nogal alpha . Een duidelijk versioning systeem ontbreekt (voorlopig?) nog voor jQuery-plugins.
Door Michel
op 07.14.06 @ 3:09 pm | Permalink
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.
Door Ivo Jansch
op 07.15.06 @ 9:42 am | Permalink
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.
Door Jeroen Bulters
op 05.17.07 @ 5:12 pm | Permalink
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>