JeuWeb - Crée ton jeu par navigateur
Rails, Ajax et éditeur de cartes - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Rails, Ajax et éditeur de cartes (/showthread.php?tid=5157)

Pages : 1 2 3


Rails, Ajax et éditeur de cartes - popayan - 09-09-2010

Salut à tous,

J'aurais besoin de conseil sur un problème liant rails, ajax et une base de données.

J'affiche un tableau contenant des images, le type de l'image affiché dépend d'une donnée en base. Chaque cellule est ajoutées à ma liste d'objet Droppable.
A coté, j'ai une seconde liste d'image Draggable.
Le but étant de déposer une de ces images dans le tableau, mettre a jour ma base de données et rafraichir le tableau ensuite.

Pour le moment, j'ai réussi à faire ca en javascript sans ajax (c'est à dire que le réaffichage se fait après rechargement de la page).

Voici un peu de code:
le code pour créer les éléments droppables
Code :
<script type="text/javascript">
    
    $$(".tile").each( function(dropElement) {
        Droppables.add(dropElement.id,
            {
                accept: 'drag',
                onDrop: function(dragged, dropped) {
                    tile_id = dropped.getAttribute('data-id');
                    type_id = dragged.getAttribute('data-id');
                    window.location = '../maps/receive_drop?tile_id='+
                        tile_id+'&type_id='+
                        type_id;
                }
            }
        );
    } );

</script>
le code de l'action appelée dans le ondrop
Code :
def receive_drop
    @map = Map.first
    @tile = Tile.find_by_id(params[:tile_id])
    @tile.update_attributes(:tile_type_id => params[:type_id])
    redirect_to('/maps/index')
end

le reste du code est assez trivial.
Donc je me pose la question de savoir qu'est ce que je dois modifier pour ne plus à avoir à rafraichir l'affichage?
Si vous avez besoin d'autres morceaux de code, demandez moi.
Si vous trouvez le code bancal, n'hésitez pas, j'aime beaucoup la critique Smile


RE: Rails, Ajax et base de données - niahoo - 09-09-2010

heu... mais tu connais le principe AJAX ou non ? Pasque si c'est non, commences par comprendre comment ça marche.

et si c'est oui, ben tu remplaces
""
window.location = '../maps/receive_drop?tile_id='+
tile_id+'&type_id='+
type_id;
""

par

""
appel de la fonction qui enregistre ();
appel de la fonction qui affiche ();
""


RE: Rails, Ajax et base de données - popayan - 09-09-2010

oui, ajax, je connais le principe Smile

Par rapport à ton commentaire, je peux dire que la "fonction qui enregistre" est déjà faite, c'est la fonction receive_drop. Par contre, je n'arrive pas à lier ca avec ajax et faire la "fonction qui affiche". En gros je veux remplacer le "window.location" par autre chose mais je ne trouve pas quoi...


RE: Rails, Ajax et base de données - Sephi-Chan - 09-09-2010

Donc ce n'est pas vraiment lié à Ruby on Rails, mais plutôt à Javascript. Quelle est la librairie Javascript que tu utilises (on dirait bien du Prototype) ?

Déjà, si tu fais de l'Ajax, tu n'as pas à te poser la question du rechargement puisque la case déplacé via Javascript sera placé au bon endroit (puisque c'est son placement à cet endroit qui entraînera l'appel asynchrone qui modifiera effectivement la ressource en base de données).

J'ai tout de même quelques conseils à te donner vis à vis du contrôleur et de l'action à utiliser.

De ce que je comprends, tu as un éditeur de carte avec des tuiles déjà présente, et tu veux changer la tuile quand on dépose une tuile (prélevé sur une palette) sur la tuile existante. C'est bien ça ? Si oui, la tuile par défaut est-elle enregistrée en base de données ?


Sephi-Chan


RE: Rails, Ajax et base de données - popayan - 09-09-2010

oui c'est prototype.

le lien avec ror est sur le fait que je n'ai pas trouvé comment faire ca correctement: j'ai bien trouvé des choses sur ce sujet ( ici, j'ai essayé de m'inspirer de la partie 4.2.2.2 ) mais pas moyen de reprendre ca dans mon code...

edit suite à la question de sephi:

oui, la map est créé en base avec une tuile par défaut (représentant de l'eau si tu veux tout savoir Wink )


RE: Rails, Ajax et base de données - Sephi-Chan - 09-09-2010

D'accord. Donc ton drag'n'drop devra plutôt déclencher l'action update du contrôleur tiles.

Je précise que le code qui vient n'est pas testé, c'est écrit à la volée. Donc à toi de l'adapter si besoin.

Donc, admettons qu'on est dans l'action edit du contrôleur maps (puisque c'est la page pour éditer une carte. Notre action devrait donc être à peu près comme ça :


class MapsController < ApplicationController
def edit
@map = Map.find(params[:id])
@tile_types = @map.tile_types # Les types de terrain disponibles pour cette carte.
end

# Reste du contrôleur…
end

Sur chaque élément du DOM qui représente une tuile, ajoute un attributs pour l'URL de modification. Exemple avec Haml (que je te conseille vivement si tu ne l'utilise pas déjà), tu auras donc une vue maps/edit.html.haml contenant quelque chose comme :


.map{ :id => "map-#{@map.id}" }
- @map.tiles.each do |tile|
= render('tiles/tile', :map => @map, :tile => tile)

%ul#toolbox
- @tile_types.each do |tile_type|
%li.tool{ :id => tile_type.id, :data => { :terrain => tile_type.terrain } }
= image_tag("terrains/#{tile_type.terrain}.png")

Il te faudra une vue partielle : crée un fichier _tile.html.haml dans le répertoire app/views/tiles/tile avec le contenu suivant :


.tile{ :id => "tile-#{tile.id}",
:class => tile.tile_type.terrain,
:data => { :url => map_tile_path(map, tile), :terrain => tile.tile_type.terrain } }


Jusque là, très simple, le code HTML de chaque tuile aura cette tronche :


<div class="tile grass" id="tile-23" data-url="/maps/1/tiles/23" data-terrain="grass" />

Et chaque outil ressemblera à :


<li id="2" data-terrain="grass">
<img src="/images/terrains/grass.png" />
</li>

Voilà pour le HTML : la carte et la palette.


Pour le Javascript, tu auras un code de ce genre :


$$(".tool").each(function(tile){
new Draggable(tile.id);
});

$$(".tile").each(function(tile){
Droppables.add(tile.id, {
onDrop: function(tool, tile, event){
var tileUrl = tile.getAttribute('data-url') + ".js";
var tileTypeId = tool.getAttribute('id');
var parameters = {
tile: {
tile_type_id: tileTypeId
}
};

new Ajax.Request(tileUrl, {
method: "put",
contentType: "text/javascript",
parameters: parameters
});

event.preventDefault();
}
});
});

Ainsi, les outils sont draggable (on peut les déplacer), et les tuiles sont droppable (elles peuvent recevoir des éléments draggable).

Quand on relâche un outil sur une tuile, la méthode onDrop est appelée, on récupère l'URL spécifiée sur la balise de la tuile ainsi que le type de terrain de l'outil. On construit également un hash de paramètres Rails ready

Ensuite, on effectue la requête Ajax. Elle utilisera la méthode PUT puisqu'on va chercher à modifier une tuile existante. On ajoute .js à l'URL pour préciser à Rails que c'est de l'Ajax. Normalement, il gère bien sans d'autant que Prototype est très fidèle aux conventions de Rails, mais n'en étant pas sûr je préfère être bullet-proof.

La vue qui sera rendu par l'appel à l'action demandée (tiles#update, en l'occurrence) devra rendre du Javascript, qui sera interprété comme tel.


class TilesController < ApplicationController
def update
@map = Map.find(params[:map_id])
@tile = @map.tiles.find(params[:id])

if @tile.update_attributes(params[:tile])
# On conserve le comportement par défaut…
else
render Confusedtatus => 500
end
end

# Reste du contrôleur…
end


Ensuite, on crée la vue pour cette action (app/views/tiles/update.js.haml) et on y place le code suivant.


html_for_tile =escape_javascript(render('tile', :map => @map, :tile => @tile))

:plain
var tile = $('#{@tile.id}');
tile.insert('#{html_for_tile}');

Là aussi c'est plutôt simple, on génère le HTML pour notre nouvelle tuile, on l'échappe pour Javascript puis on le place dans une variable Ruby html_for_tile.

Ensuite, on génère la partie Javascript de notre vue (puisque rappelons-nous que le texte qui sera produit par notre vu sera utilisé en guise de réponse à la requête asynchrone et interprété comme du Javascript) dans un bloc :plain pour que seules les expression de la forme #{expression} soient interprétées par Ruby.

Et voilà, on a notre éditeur de carte ! Smile


Sephi-Chan


RE: Rails, Ajax et base de données - popayan - 09-09-2010

Déjà merci beaucoup pour le temps passé sur mon problème, ca fait plaisir ^^

J'ai commencé à étudier tout ça, je pense que ca va répondre exactement à mes besoins.
Une question tout de même concernant un bout de code:

Code :
.tile{ :id    => "tile-#{tile.id}",
       :class => tile.tile_type.terrain,
       :data  => { :url => map_tile_path(map, tile), :terrain => tile.tile_type.terrain } }

Si je comprends bien, ce bout de code créé une div avec 4 attributs mais il n'affiche rien?
j'essaie également d'écrire la route map_tile_path dans mon fichier de routes.rb, je pense que j'en suis pas loin, mais c'est pas encore ca :p


RE: Rails, Ajax et base de données - Sephi-Chan - 09-09-2010

Le fragment de code Haml est interprété (pour peu que ta vue s'appelle machin.truc.haml, qui indique à Ruby que cette vue doit être interprétée avec Haml (alors que les vues machin.truc.erb sont interprétées par le moteur Erb) est produit du HTML (qui a la particularité d'être parfait aussi bien en terme de validité que de rendu à l'indentation).

Ici, on met ça dans une vue partielle, qu'on différencie d'une vue classique par son nom préfixé d'un underscore et qu'on invoque grâce à la méthode render. On fait ceci afin de pouvoir réutiliser cette vue partielle plus tard, notamment dans la vue de l'action, pour éviter de dupliquer du code bêtement : si le HTML d'une tuile change, il suffit de modifier la vue partielle.

Pour les routes, il faut définir une ressource maps qui dispose de ressources tiles afin de générer les 7 routes pour les maps et les 7 routes pour les tuiles.


resources :maps do
resource :tiles
end


Sephi-Chan


RE: Rails, Ajax et base de données - niahoo - 09-09-2010

non,

ça c'est une fonction qui enregistre :
Code :
new Ajax.Request(tileUrl, {
        method:      "put",
        contentType: "text/javascript",
        parameters:  parameters
      });

      event.preventDefault();

Tout ce que je vois qui s'appelle receive_drop dans ton code c'est dans une URL. Je suppose donc que tu me parlais d'une fonction PHP. C'est pour ça que je te demandais si tu avais bien saisi le principe, car à aucun moment tu ne faisais d'envoi de données..

Bon par contre je connais mal prototype donc je ne vois pas à quel moment on récup les données Sephi dans ton js.


RE: Rails, Ajax et base de données - popayan - 09-09-2010

(09-09-2010, 03:54 PM)Sephi-Chan a écrit : Le fragment de code Haml est interprété (pour peu que ta vue s'appelle machin.truc.haml, qui indique à Ruby que cette vue doit être interprétée avec Haml (alors que les vues machin.truc.erb sont interprétées par le moteur Erb) est produit du HTML (qui a la particularité d'être parfait aussi bien en terme de validité que de rendu à l'indentation).

Ici, on met ça dans une vue partielle, qu'on différencie d'une vue classique par son nom préfixé d'un underscore et qu'on invoque grâce à la méthode render. On fait ceci afin de pouvoir réutiliser cette vue partielle plus tard, notamment dans la vue de l'action, pour éviter de dupliquer du code bêtement : si le HTML d'une tuile change, il suffit de modifier la vue partielle.

J'ai dû mal poser ma question... j'ai bien compris le principe. La question était que la <div> a bien des attributs mais son contenu est vide?

voici le code que ca génère:
Code :
<div class='map' id='map-1'>
  <div class='grass_01 tile' data-terrain='grass_01' data-url='/maps/1/tiles/1' id='tile-1'></div>
  <div class='grass_01 tile' data-terrain='grass_01' data-url='/maps/1/tiles/2' id='tile-2'></div>
  <div class='grass_01 tile' data-terrain='grass_01' data-url='/maps/1/tiles/3' id='tile-3'></div>
</div>