SpringBoot-VueJS
adds VueJS
to a Spring-boot
Project for Creating Client-Side Application Logic Within Spring Controllers.
Insert the dependency in your pom.xml
file:
<dependency>
<groupId>io.github.jeemv.springboot.vuejs</groupId>
<artifactId>springboot-vuejs</artifactId>
<version>[1.0,)</version>
</dependency>
@Controller
@RequestMapping("/ui/")
public class UiTest {
@GetMapping("test")
public String index(ModelMap model) {
VueJS vue=new VueJS("#app");
vue.addData("message", "Hello world!");
model.put("vue", vue);
return "index";
}
}
The index.html
mustache view:
<div id="app">
<%message%>
<input v-model="message">
</div>
{{{vue}}}
Mustache view use double mustache for variables (message in the example), so the VueJS instance is set by default to use <%
and %>
as delimiters.
The vue
variable generates the javascript code for the view instance creation. The triple mustache {{{vue}}}
is use for javascript/html code unescaping.
This technique has the advantage of providing a globale instance of VueJS for all the actions of a controller. Create a configuration class to allow the autowiring of VueJS:
@Configuration
@ComponentScan("io.github.jeemv.springboot.vuejs")
public class AppConfiguration {
}
In your controller:
@Controller
@RequestMapping("/ui/")
public class UiTest {
@AutoWired
private VueJS vue;
@ModelAttribute(name = "vue")
private VueJS getVue() {
return this.vue;
}
@GetMapping("test")
public String index(ModelMap model) {
vue.addData("message", "Hello world!");
return "index";
}
}
In this case, you can directly configure VueJS in the application.properties file:
springboot.vuejs.delimiters=<%,%>
springboot.vuejs.axios=true
springboot.vuejs.el=v-app
For a more punctual use, in a single method for example, It is possible to use the @ModelAttribute annotation :
@Controller
@RequestMapping("/ui/")
public class UiTest {
@ModelAttribute("vue")
public VueJS getVue() {
return new VueJS("#app");
}
@GetMapping("test")
public String index(@ModelAttribute("vue") VueJS vue) {
vue.addData("message", "Hello world!");
return "index";
}
}
AOP loading in pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP activation in app config file:
@Configuration
@ComponentScan("io.github.jeemv.springboot.vuejs.aspects")
@EnableAspectJAutoProxy
public class AppConfig {
}
AOP usage in controller:
@Controller
@RequestMapping("/ui/")
public class UiTest {
@GetMapping("test")
@VueJSInstance
public String test2(VueJS vue,ModelMap model) {
vue.addData("message", "Hello world!");
return "index";
}
}
Adds data object for the Vue instance.
vue.addData("visible",false);
vue.addData("group",group);//where group is an instance of the class Group
vue.addData("users",users);//where users is an ArrayList of User
Adds a method to the vue instance
vue.addMethod("toggleVisible", "this.visible=!this.visible;");
vue.addMethod("addScore","this.scores.push(score)","score");
Adds a computed property to the vue instance
vue.addComputed("count", "return this.users.length");
A computed property can have a setter:
vue.addMethod("fullname","return this.firstName + ' ' + this.lastName;","var names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[names.length - 1];");
Adds a watcher on variable to the view instance
vue.addWatcher("value", "'value was '+oldValue+'. It is now '+val;");
It is possible to create components at runtime, but it is more efficient to generate them before:
public class CompoButton {
public static void main (String[] args) throws java.lang.Exception {
VueComponent compo=new VueComponent("button-counter");
compo.addData("count", 0);
compo.setTemplate("<button @click=\"count++\">You clicked me {{ count }} times.</button>");
compo.createFile(false);
}
}
The generated file is created in {project-folder}/src/main/resources/static/vueJS/button-counter.js
//Script generated with VueComponent at Thu Oct 11 03:01:09 CEST 2018
Vue.component('button-counter',{
"data":function() {
return {
"count":0
};
}
,"template":"<button @click=\"count++\">You clicked me {{ count }} times.</button>"
}
);
Usage:
<script src="/vueJS/button-counter.js"></script>
...
<button-counter></button-counter>
Templates are easier to create in a file:
Create the file /src/main/resources/templates/vueJS/button-counter.html
<button @click="count++">
You clicked me {{ count }} times.
</button>
Modify the class CompoButton
:
public class CompoButton {
public static void main (String[] args) throws java.lang.Exception {
VueComponent compo=new VueComponent("button-counter");
compo.addData("count", 0);
compo.setDefaultTemplateFile();
compo.createFile(false);
}
}
the generated file is the same, but the method is more convenient.
Default delimiters are <%
and %>
.
For changing the plain text interpolation delimiters and avoid conflict with other template packages, you can modify them with:
vue.setDelimiters("{!","!}");
You can also generate code to perform ajax queries:
vue.addMethod("submit",Http.postForm("formRef","console.log('submit datas!')"));
HTTP calls from Vue.js to SpringBoot REST backend:
vue.addMethod("saveUser",Http.post("user/","user","console.log('User added!')"),"user");
vue.addMethod("updateUser",Http.put("user/","user","console.log('User updated!')"),"user");
For axios, Do not forget to include the corresponding js file.
The javascript code is sometimes too large to be neatly integrated into a java controller. In this case, it can be delocalized in a javascript file, which can refer to java variables of the controller.
The java variables are parsed with ${varName}
usage.
//resource/static/js/sample.js
console.log("${message}");
@GetMapping("sample")
public String testJs(@ModelAttribute("vue") VueJS vue) throws IOException {
JavascriptResource js = JavascriptResource.create("sample");
js.put("message", "Hello world!");
vue.addMethod("click", js.parseContent());
return "view";
}
To avoid the multiplicity of javascript files, it is possible to group several scripts in the same file.
Each script (qualified as a module) must be identified in the javascript file by a comment on a single line bearing its name, and a comment marking the end (also mentioning the name of the script).
Each script can possibly be isolated, which is without consequences.
//resource/static/js/multi.js
//----------------consoleMsg-----------------------
console.log("${message}");
//----------------consoleMsg (end)-----------------
//----------------alertMsg-------------------------
(function(){
alert("${message}");
})();
//----------------alertMsg (end)-------------------
@GetMapping("sample")
public String testJsMulti(@ModelAttribute("vue") VueJS vue) throws IOException {
JavascriptMultiModulesResource jsMulti=JavascriptMultiModulesResource.create("multi");
jsMulti.getModule("consoleMsg").put("message", "This is a console message");
vue.addMethod("click", js.parseContent("consoleMsg"));
return "view";
}