Cet article est publié sous licence CC BY-NC-SA
Dans un article précédent nous avons découvert EmberJS et son fonctionnement de base. Nous allons, cette fois, voir comment créer une single-page-application avec l’aide de RubyOnRails.
Avant de commencer le développement de notre application, prenons quelques secondes pour parler de deux outils très utiles lorsque vous utilisez EmberJS.
Le premier est Ember Data. C’est une librairie externe qui, dans une application utilisant EmberJS, permet de gérer la persistance des données en mappant celles-ci aux modèles par exemple. Cette libraire apporte également des adapters afin de permettre la connexion avec une API. Attention toutefois, Ember Data est encore en version bêta, elle est cependant déjà intégrée à la gem ember-rails et les contributeurs sont très actifs.
Le second outil que j’utilise, et qui est très appréciable, est un langage de templating qui ressemble fortement à Slim , il s’agit d’Emblemjs. C’est une alternative plutôt sympathique à Handlebarjs et cela permet d’avoir des templates plus clairs et lisibles.
Nous allons maintenant pouvoir nous lancer dans le développement de notre single-page-application avec EmberJS et RubyOnRails.
Afin de pouvoir afficher les données et permettre à l’utilisateur de les manipuler, il faut commencer par créer l’API qui nous permettra de faire le lien entre la base de données et EmberJS. L’application développée ici, pour le test, aura pour but de lister des évènements sportifs (course à pied dans notre cas). Au fur et à mesure des articles nous pourrons ajouter différentes fonctionnalités.
Après avoir créé votre application via la commande rails new my_app_name
et créer la base de données (lancer la commande rake db:create
après avoir renseigné les identifiants de connexion à la base dans le fichier config/database.yml
).
Commençons par créer un controller et une vue pour la homepage de notre application :
# app/controllers/home_controller.rb
class HomeController < ApplicationController
def index
end
end
/ app/views/home/index.html.haml
%h1 Liste des courses
%p liste à venir...
Il faut également définir la route permettant d’arriver sur cette page :
# config/routes.rb
EmberjsSinglePageApp::Application.routes.draw do
root "home#index"
end
Si vous lancer la commande rails s
et que vous vous rendez à l’url localhost:3000
dans un navigateur vous pouvez constater que c’est bien notre page qui est affichée.
Maintenant, nous allons créer le premier modèle et ajouter les routes nécessaires :
rails g model race name:string description:text city:string county:string country:string start_at:datetime
Cette commande a généré le modèle et la migration nécessaire, il suffit de lancer rake db:migrate
pour créer la table dans la base de données. Dans le fichier routes.rb
, nous ajoutons donc notre nouveau modèle. Nous utilisons un namespace pour ne pas avoir de soucis avec les urls par la suite :
EmberjsSinglePageApp::Application.routes.draw do
namespace :api do
resources :races
end
...
end
EmberJS s’attend à recevoir des données JSON à un format bien précis, nous allons donc utiliser la gem active_model_serializers pour formatter ces données :
gem 'active_model_serializers', github: 'rails-api/active_model_serializers'
Ensuite, le serializer de notre modèle correspond à cela :
class RaceSerializer < ActiveModel::Serializer
attributes :id,
:name,
:description,
:city,
:county,
:country,
:start_at
end
Il faut également ajouter le controller (dans un sous-répertoire api) contenant les actions qui permettront de manipuler nos courses. Il nous renverra des données en JSON.
# app/controllers/api/races_controller.rb
class Api::RacesController < ApplicationController
respond_to :json
def index
@races = Race.all
respond_with @races
end
def show
@race = Race.find params[:id]
respond_with @race
end
def create
@race = Race.new race_parameters
@race.save
respond_with @race, location: api_race_path(@race)
end
def update
@race = Race.find params[:id]
@race.update_attributes race_parameters
respond_with @race, location: api_race_path(@race)
end
def destroy
@race = Race.find params[:id]
@race.destroy
respond_with @race
end
private
def race_parameters
params.require(:race).permit(:name, :description, :city, :county, :country, :start_at)
end
end
Si vous souhaitez vérifier le bon fonctionnement, vous pouvez vous rendre à l’url localhost:3000/api/races.json
et constater que c’est un tableau vide qui vous est pour le moment retourné.
Nous avons donc maintenant la possibilité de récupérer en JSON toutes les courses ou bien une seule et d’en créer, modifier ou supprimer une. Cependant, il n’y a, pour le moment, aucune interface graphique permettant d’appeler ces actions du controller.
Il faut commencer par ajouter les gems nécessaires à votre Gemfile puis lancer la commande bundle install
:
gem 'ember-rails' gem 'ember-source' gem 'emblem-rails'
La commande rails g ember:bootstrap --javascript-engine coffee
permet de finaliser l’installation d’EmberJS et de générer les fichiers nécessaires.
Nous précisons dans le fichier app/assets/javascripts/store.js.coffee
l’adapter que nous allons utiliser :
DS.RESTAdapter.reopen
namespace: "api"
EmberjsSinglePageApp.Store = DS.Store.extend
adapter: DS.RESTAdapter.create()
On spécifie le namespace afin d’appeler les bonnes URLs depuis Ember.
Nous créons, dans les répertoires spécifiques à EmberJS, le modèle et le fichier de routes pour l’index de notre modèle Rails Race :
# app/assets/javascripts/models/race.js.coffee
EmberjsSinglePageApp.Race = DS.Model.extend
name: DS.attr('string')
description: DS.attr('string')
city: DS.attr('string')
county: DS.attr('string')
country: DS.attr('string')
start_at: DS.attr('date')
# app/assets/javascripts/routes/races/index.js.coffee
EmberjsSinglePageApp.RacesRoute = Ember.Route.extend
model: ->
@store.find('race')
Enfin dans le fichier de routes global d’Ember il faut spécifier, comme pour Rails, que l’on veut créer des routes pour un modèle donné :
EmberjsSinglePageApp.Router.map ()->
@resource('races', -> {})
Attention, si vous utilisez uniquement @resource('races')
sans passer de fonction comme deuxième argument, la route ne sera pas créée et EmberJS utilisera donc ResourceRoute, ResourceController et la template resource (ce sont des valeurs par défaut). Si vous souhaitez utilisez des valeurs différentes ou même spécifier le template à utiliser vous devez donc passer une fonction en paramètre à @resource
(même une fonction vide comme dans l’exemple ci-dessus).
Il ne manque plus que le premier template (app/assets/javascripts/templates/races/index.emblem
) et la vue associée (app/assets/javascripts/views/races/index.js.coffee
) à créer :
/ Template emblemjs h2 Listes des courses ul each model li= name
# View associated to index action
EmberjsSinglePageApp.RacesIndexView = Ember.View.extend
templateName: "races/index"
Comme vous pouvez le constater, dans un souci de clarté, j’ai créer des sous répertoires pour les vues et les templates (et également pour les controllers que nous allons voir par la suite).
En vous rendant à l’url localhost:3000/#/races
votre observez votre template.
Je vous conseille d’utiliser le débugger EmberJS de Chrome, il s’avère être vraiment pratique en cas de soucis.
Maintenant que l’index fonctionne, le but est de pouvoir créer, voir et modifier des objets correspondant à notre modèle Race. Pour cela nous allons commencer par créer une route correspondant à l’action new ainsi que la vue et le template associés. Il sera également nécessaire de créer un controller pour gérer les actions de cette vue. Dans le router global d’EmberJS, on ajoute donc la route new dans le contexte de notre modèle Race :
# app/assets/javascripts/router.js.coffee
EmberjsSinglePageApp.Router.map ()->
@resource('races', ()->
@route('new')
)
On obtient donc une route localhost:3000/#/races/new
qui pour le moment ne correspond à rien. Il faut dans un premier temps créer le router spécifique à cette action. Ce dernier ne contient rien de bien complexe, on définit le modèle et on initialise le controller :
EmberjsSinglePageApp.RacesNewRoute = Ember.Route.extend
model: ->
@store.find 'race'
setupController: (controller) ->
controller.set 'model'
La vue est semblable à celle existante pour l’index :
EmberjsSinglePageApp.RacesNewView = Ember.View.extend
templateName: "races/new"
Elles sont utiles car les fichiers de templates sont dans un sous-répertoire, si ce n’était pas le cas alors ces fichiers de vue seraient inutiles car les valeurs par défaut seraient correctes.
Enfin, il nous faut créer un template pour cette action new :
h2 Créer une nouvelle course form submit="createRace" label Nom input name="name" br label Description textarea name="description" br label Ville input name="city" br label Région input name="county" br label Pays input name="country" br label Début input name="start_at" br button action="createRace" Créer
On retrouve donc nos inputs pour les attributs du modèle ainsi que le formulaire et un bouton. Ces deux derniers ont comme action associée createRace qui va devoir être définie dans le controller :
# app/assets/javascripts/controllers/races/new.js.coffee EmberjsSinglePageApp.RacesNewController = Ember.ObjectController.extend actions: createRace: -> params = $("form").serializeArray() race = EmberjsSinglePageApp.Race.createRecord({}) for param in params race.set(param.name, param.value) race.get("store").commit() @transitionToRoute('races')
Cette action récupère donc les valeurs des champs du formulaire et construit un objet Race avec ces attributs. Ensuite, on sauve cet objet via la ligne race.get("store").commit()
. Une requête vers l’API va être effectuée à ce moment là pour insérer l’objet dans la base de données. La dernière action @transitionToRoute('races')
est une redirection vers la page d’index. Il n’y a pour le moment aucune validation sur notre modèle Rails donc aucun problème à ce niveau.
Vous pouvez vous rendre à l’URL localhost:3000/#/races/new
et créer votre première course, vous la voyez ensuite apparaître dans la liste sur l’index.
Après avoir pu lister et créer des objets, nous allons passer à l’action show. Encore une fois, on commence par ajouter la route :
EmberjsSinglePageApp.Router.map ()->
@resource('races', ()->
@route('new')
@resource('race', { path: ':race_id' })
)
La route localhost:3000/#/races/:race_id
est maintenant créée. Il suffira de lui passer en paramètre un ID existant.
Le router spécifique à cette action va, dans le cas présent, récupérer l’objet et initialiser le controller :
EmberjsSinglePageApp.RaceRoute = Ember.Route.extend
model: (params) ->
@store.find 'race', params.race_id
setupController: (controller, race) ->
controller.set 'model', race
La vue reste dans la lignée des précédentes et ne sert qu’à spécifier le fichier de template. Ce dernier va afficher les attributs de l’objet choisi :
h2= name p= description p Lieu : #{city} - #{county} (#{country})
Le controller est vide dans le cas présent puisqu’aucune action n’est nécessaire, le fichier ne contient donc que la ligne EmberjsSinglePageApp.RaceController = Ember.ObjectController.extend
.
L’action show est donc à présent utilisable en accèdant à l’url d’un objet donné, par exemple localhost:3000/#/races/1
.
Maintenant que nous pouvons lister, créer et voir un objet Race, nous allons le modifier. La ligne nécessaire est ajoutée au router :
EmberjsSinglePageApp.Router.map ()->
@resource('races', ()->
@route('new')
@resource('race', { path: ':race_id' })
@route('edit', { path: '/:race_id/edit' })
)
Comme pour l’action show, le router spécifique à l’action va récupérer l’objet correspondant à l’ID donnée et initialiser le controller. Ce dernier, comme pour l’action new, va permettre de définir une action afin de modifier l’objet :
EmberjsSinglePageApp.RacesEditController = Ember.ObjectController.extend
actions:
updateRace: ->
race = @get('model')
params = $("form").serializeArray()
for param in params
race.set(param.name, param.value)
race.get("store").commit()
@transitionToRoute('race', race)
Les actions de base étant créées, il serait plus pratique d’avoir des liens entre ces pages pour avoir une vraie navigation. Avec EmberJS, il est très simple de générer des liens, il n’y a pas besoin de récupérer l’évènement click sur un lien pour changer d’URL et donc de vue comme c’est le cas dans Backbone. En effet, linkTo
permet, dans un template Emblemjs, de créer des liens entre les pages. Commençons par ajouter des liens sur l’index :
h2 Listes des courses ul each model li = linkTo "race" this | #{name} else li Il n'y a aucune courses pour le moment. = linkTo "races.new" | Créer une course
Vous remarquerez que les routes sont à passer avec une syntaxe spécifique (c’était déjà le cas avec la méthode transitionToRoute
dans les controllers). Pour le lien vers l’action new, on spécifie simplement races.new
mais pour l’action show il faut passer en paramètre l’objet : linkTo "race" this | #{name}
. Attention, il n’y a pas de séparateur entre la route et l’objet. La gestion des liens est donc très simple une fois que l’on a compris la syntaxe des routes. Je vous laisse la possibilité d’ajouter des liens où vous le souhaitez sur les autres pages.
Nous allons nous attardez sur le template correspondant à l’action show et y ajouter un bouton pour supprimer un objet, ce qui constituera la dernière étape de cet article.
Après avoir créer et modifier des objets, il faut aussi pouvoir en supprimer. Commençons par ajouter le bouton dans le template avec une action que nous définirons ensuite :
h2= name p= description p Lieu : #{city} - #{county} (#{country}) button{action "delete"} Supprimer la course br = linkTo "races.edit" this | Modifier la course br = linkTo "races" | Retour à la liste des courses
Dans le controller, nous créons l’action delete correspondante :
EmberjsSinglePageApp.RaceController = Ember.ObjectController.extend
actions:
delete: ->
race = @store.find('race', @get('model.id'))
race.deleteRecord()
race.save()
@transitionToRoute('races')
Il existe une méthode deleteRecord()
qui permet de supprimer l’objet de la collection mais cela ne le supprime pas en base, il faut utiliser save()
pour faire un appel à l’API et définitivement supprimer l’objet.
Voilà, vous avez tout en main pour gérer un modèle dans EmberJS. Vous pouvez retrouver le code correspondant à l’application de l’article sur notre page Github. Dans les prochains articles, nous repartirons de cette base pour aller plus loin dans le fonctionnement d’EmberJS.
L’équipe Synbioz.
Libres d’être ensemble.
Nos conseils et ressources pour vos développements produit.