Créer la branche td4 à partir de la branche td3 de votre projet (reprenez éventuellement la correction).
Lire APIs web
Ajouter la dépendance Rest Data à pom.xml :
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-rest -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
<version>3.0.4</version>
</dependency>
Configurer l'api REST pour qu'elle expose les identifiants de certaines entities en créant une nouvelle classe de configuration :
@Configuration
class RestConfig:RepositoryRestConfigurer {
override fun configureRepositoryRestConfiguration(config: RepositoryRestConfiguration?, cors: CorsRegistry?) {
config?.exposeIdsFor(Master::class.java, Dog::class.java)
}
}
Créer une nouvelle interface RestMasterResource héritant de JpaRespository, annotée avec @RestResourceController :
@RepositoryRestResource(collectionResourceRel = "masters", path = "masters")
interface RestMasterResource:JpaRepository<Master, Int> {
}
Même chose pour les Dogs :
@RepositoryRestResource(collectionResourceRel = "dogs", path = "dogs")
interface RestDogResource:JpaRepository<Dog, Int> {
}
@Entity
open class Dog() {
constructor(name:String):this(){
this.name=name
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@JsonSerialize
open var id = 0
@Column(length = 30, nullable = false)
open var name: String=""
@ManyToOne(optional = true)
@RestResource(exported = false,rel = "master", path = "master")
@JsonBackReference
open var master: Master?=null
@ManyToMany
open val toys= mutableSetOf<Toy>()
}
Tester les requêtes suivantes avec Reqbin
| Méthode | URL | Data | Résultat |
|---|---|---|---|
| Get | /dogs | Liste des chiens | |
| POST | /dogs | {“name”:“Rex”} | Ajout d'un chien |
| PUT | /dogs/1 | {“name”:“Rex”, master: {id: 1}} | Modification complète d'un chien |
| PATCH | /dogs/1 | {“name”:“Rexa”} | Modification partielle d'un chien |
| DELETE | /dogs/1 | Suppression d'un chien |
| Méthode | URL | Data | Résultat |
|---|---|---|---|
| Get | /masters | Liste des maîtres | |
| POST | /masters | {“firstname”:“John”, “lastname”: “DOE”} | Ajout d'un maître |
| PUT | /masters/1 | {“firstname”:“John”, “lastname”: “DOE”} | Modification complète d'un maître |
| PATCH | /masters/1 | {“firstname”:“Johnny”} | Modification partielle d'un maître |
| DELETE | /masters/1 | Suppression d'un maître |
Ajouter la dépendance Springboot-vuejs dans pom.xml :
<dependency>
<groupId>io.github.jeemv.springboot.vuejs</groupId>
<artifactId>springboot-vuejs</artifactId>
<version>1.1.11</version>
</dependency>
Ajouter une classe de configuration pour permettre l'injection des instances de VueJS :
@Configuration
@ComponentScan("io.github.jeemv.springboot.vuejs")
class AppConfig {
}
Ajouter la configuration suivante dans application.properties :
##vuejs
springboot.vuejs.delimiters={!,!}
springboot.vuejs.axios=true
springboot.vuejs.el=#app
springboot.vuejs.vuetify=false
springboot.vuejs.vue-version=3.*
springboot.vuejs.http-data=response.data._embedded
Inégration de vueJS et axios dans footer.html :
<script src="https://cdn.jsdelivr.net/npm/axios@1.3.4/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue@3.2.47/dist/vue.global.min.js"></script> </body> </html>
Créer un contrôleur SPAController :
@Controller
class SPAController {
@Autowired
lateinit var vue: VueJS
@ModelAttribute("vue")
fun vue(): VueJS = vue
}
Les données seront chargées lorsque l'objet vueJs est mounted, par une requête Ajax de type GET, alimentant le tableau this.masters :
@Controller
@RequestMapping("/spa")
class SPAController {
@Autowired
lateinit var vue: VueJS
@ModelAttribute("vue")
fun vue(): VueJS = vue
@GetMapping(path = ["/","","/index"])
fun index(): String {
vue.addDataRaw("masters", "[]")
vue.onMounted(
Http.get("/masters",
Http.responseArrayToArray("this.masters","masters"),
"console.log('Erreur sur chargement des données!');"
)
)
return "/spa/index"
}
}
Template /spa/index.html :
<h1>Maîtres <span class="ui label">{!masters.length!}</span></h1>
<ul>
<li v-for="master in masters">
{!master.firstname!} {!master.lastname!}
</li>
</ul>
{{> /main/footer }}
{{{vue}}}
L'appel de {{{vue}}} permet d'insérer le code vueJS généré dans la page.
Alternative
@Controller
@RequestMapping("/spa")
class SPAController {
@Autowired
lateinit var masterRepository: MasterRepository
@Autowired
lateinit var vue: VueJS
@ModelAttribute("vue")
fun vue(): VueJS = vue
@GetMapping(path = ["/","","/index"])
fun index(): String {
vue.addData("masters", masterRepository.findAll())
return "/spa/index"
}
}
Ajout d'une méthode de suppression d'un master, sur l'instance de Vue, dans l'action index de SPAController :
vue.addMethod("remove",
Http.delete("'/masters/'+master.id",
JsArray.remove("this.masters","master")+
JsArray.addAll("this.dogs","master.dogs")+
"console.log(`Maître \${master.firstname} supprimé!`);",
"console.log('Erreur sur suppression de master!');"
),
"master")
Template /spa/index.html avec appel de la méthode remove :
<h1>Maîtres <span class="ui label">{!masters.length!}</span></h1>
<ul>
<li v-for="master in masters">
{!master.firstname!} {!master.lastname!} <i class="remove icon" @click="remove(master)"></i>
</li>
</ul>
{{> /main/footer }}
{{{vue}}}
Composant pour gérer l'affichage d'un message, ayant les propriétés suivantes :
| Propriété | Type | Rôle |
|---|---|---|
| visible | boolean | Détermine si l'élément est visible |
| error | boolean | Détermine le type de message, et d'icône |
| title | string | Titre du message |
| content | string | Texte du message |
Template HTML (basé sur le ui message de semantic) :
Création du composant global sur l'instance de Vue dans le contrôleur :
vue.addGlobalComponent("AppMessage").setProps("title","content","error","visible").setDefaultTemplateFile()
Exemple d'utilisation dans un template :
<app-message title="Titre du message" content="Tout va bien !" :error="false" :visible="true"></app-message>
Ajout de méthodes à l'instance de vue pour faciliter l'affichage des messages :
vue.addDataRaw("message","{title:'',content:''}")
vue.addMethod("showMessage",
"this.message={error: error,title: title, content: content, display: true};"+
"setTimeout(function(){this.message.display=false;}.bind(this),5000);",
"title","content","error")
vue.addMethod("successMessage",
"this.showMessage(title,content,false);",
"title","content")
vue.addMethod("errorsMessage",
"this.showMessage(title,content,true);",
"title","content")
Implémenter le comportement de l'interface du TD précédent (Dogs ou Stories) dans le contrôleur Spring:
| Data | Rôle | Initial value |
|---|---|---|
| masters | Liste des maîtres (Developers) | [] |
| dogs | Liste des chiens SPA (Stories sans dev) | loaded with repository |
| master | Le maître (dev) à ajouter | nouvelle instance |
| masterId | L'id de master sélectionné dans la liste d'affectation SPA | |
| dogNames | Liste des noms de chiens à adopter | [] |
| dog | Le chien à ajouter | nouvelle instance |
| message | Le message à afficher après une opération | nouvelle instance |
| Method | Rôle | Opérations |
|---|---|---|
| addMaster(master) | Ajoute le maître passé en paramètre |
|
| remove(master) | Supprime le maître passé en paramètre | DELETE vers /masters/{id} |
| addDog(master,dogName,index) | Le maître adopte un chien | POST une instance de dog vers /dogs |
| giveup(master,dogName) | Le maître Abandonne le chien dont le nom est passé en paramètre | PUT vers /dogs/{id} |
| removeDog(dog) | Supprime le chien SPA passé en paramètre | DELETE vers /dogs/{id} |
| adopt(gog,masterId) | Le chien est adopté par le maître dont l'id est passé en paramètre | PUT vers /dogs/{id} |