Introducing Vuecket

Vuecket – web framework integrating VueJS from the client side and Apache Wicket from the server room. It takes the best of both and makes developing full-stack applications even faster and easier. Of course, these are all big words, because Vuecket is currently (August 2020) less than a month old, and it has not yet passed the baptism of “fire and blood” production servers. But it has already included all the best that we have developed during the development of our key Open Source product. Orienteer(platform for rapid development of business applications). And it is precisely because of its young age that Vuecket needs your help: please share what you liked, what is not very good, where improvements are needed, etc.

The basic principles that guide us when building Vuecket are:

  1. Being declarative is not imperative. Vuecket doesn’t dictate any special code requirements. It can be applied fairly quickly and easily to existing Vue.JS or Apache Wicket projects.
  2. Follow the Pareto principle. Vuecket should provide 80% of the functionality you want out of the box, but there should be good and convenient extension points for the remaining 20%.

It’s easy to see that these principles also apply to Vue.JS and Apache Wicket.

So how exactly are we going to get started with Vuecket? I suggest making a chat / guest board with Markdown support. I will not torment too much: the finished application hereand the code here

We create a project

Let’s generate our project via `mvn archetype: generate`. To do this, you can use, for example, the following command:

mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=8.9.0 -DgroupId=com.mycompany -DartifactId=mychat -DarchetypeRepository=https://repository.apache.org/ -DinteractiveMode=false

Vuecket has not yet got its own Maven project template. Maybe, in future, we’ll add this too. Now let’s connect Vuecket itself. Add the following dependency to the project `pom.xml`:


	org.orienteer.vuecket
	vuecket
	1.0-SNAPSHOT

Outputting text in Markdown

The Wicket project already contains a Wicket welcome page by default. Let’s add some code to it to make sure Vuecket is already working. For example, let’s display Hello World, but in Markdown, and so that the text itself is set on the server side in the Apache Wicket component. For rendering Markdown we will use the library vue-markdown

In HomePage.html, instead of the Wicket greeting, add:

This will be replaced

And in HomePage.java the following code:

public HomePage(final PageParameters parameters) {
	super(parameters);
	add(new VueComponent("app")
			.add(new VueMarkdown("markdown", "# Hello World from Vuecket")));
}

But where is the VueMarkdown class? And we will define it as follows:

@VueNpm(packageName = "vue-markdown", path = "dist/vue-markdown.js", enablement = "Vue.use(VueMarkdown)")
public class VueMarkdown extends Label {
	public VueMarkdown(String id) {
		super(id);
	}
	public VueMarkdown(String id, Serializable label) {
		super(id, label);
	}
}

Pay attention to the @VueNpm annotation. It is needed to enable Vuecket on this Wicket component, which will load everything needed from NPM to help the browser render the already Vue component for Markdown correctly.

If you did everything correctly, then after starting the project through `mvn jetty: run` you should see something like this on http: // localhost: 8080

So what happened here and why does it work?

  • We marked up the page by adding 2 Vue components to it: for the application and for the Markdown output
  • We have bundled Vue components with Wicket components on the server side (in HomePage.java)
  • We told Vuecket which Vue.JS library it needs to render ‘vue-markdown’
  • And then everything is simple: Wicket, when rendering the page to the browser, used the line “# Hello World from Vuecket”, which we set when adding the Wicket component, and Vuecket helped the browser load the necessary VueJS libraries, launch the VueJS application and render the greeting already as rendered Markdown

Github commit to help

Entering a message and previewing it

In this step, we will complicate our application: we will make the message input and preview.
Let’s add a textarea to HomePage.html for entering a message, as well as bind this field and vue-markdown to the VueJS “text” variable.

Will be replaced

We are already using the “text” variable, but now we need to add it to the data Vue component. There are several ways to do this in Vuecket, but let’s go for the longest:

  • Create your own VueComponent for Vue application
  • Let’s associate it with our * .vue file
  • Let’s write the logic in the * .vue file: for now, just the “text” field

Here are some of the changes we’ll make:

//HomePage.java:
public HomePage(final PageParameters parameters) {
	super(parameters);
	add(new ChatApp("app")
			.add(new VueMarkdown("markdown")));
}
//ChatApp.java:
@VueFile("ChatApp.vue")
public class ChatApp extends VueComponent {
	public ChatApp(String id) {
		super(id);
	}
}

Well, ChatApp.vue itself:


As a result, when you start `mvn jetty: run` and enter some text, you can see the following

In this chapter, we learned how to: create familiar * .vue files and associate them with Apache Wicket components

GitHub commit to help

Display a list of messages and add a new one

There won’t be anything Vuecket or Wicket specific in this chapter: pure VueJS shine.
If we decompose the task, then we will need to do the following:

  • Add a list box to our Vue application to save messages
  • Add a method to add a new message to the list
  • Display a list of messages and don’t forget about markdown

First, let’s change our ChatApp.vue and add the necessary logic: a new `messages` field with a list of messages and the` addMessage` method to add a new message. And let’s not forget that when adding a message to the list, it’s a good idea to clear the original input field. For messages, we will store not only the text, but also the date of adding / sending. In the future, it will be possible to expand with additional fields, for example, who sent this message, priority, required highlighting, etc.


We will also change HomePage.html, add a display of the list of messages and add a call to our addMessage method when pressing Ctrl-Enter.

{{ index }}: {{ m.date }}
Will be replaced

When you run `mvn jetty: run` and enter a few messages, you will see something like this

In this chapter, we have only taught the application using VueJS to add a message to the list and display the latter.

GitHub commit to help

Turn on collaboration

If before that the content of our guestbook was unique for each visitor of the page, then in this chapter we will enable communication with the server and allow synchronization with all visitors. For this we need Vuecket Data Fibers, a solution that allows browser-side objects to be synchronized with server-side objects. And the most interesting thing is that we don’t need to do anything for this on the client side! Sounds cool? Let’s go code! Although … There are only two new lines in our ChatApp.java component:

private static final IModel> MESSAGES = Model.ofList(new ArrayList());

public ChatApp(String id) {
	super(id);
	addDataFiber("messages", MESSAGES, true, true, true);
}

What happened here:

  • We have created a MESSAGES model that is available to everyone, since it is created as static final.
  • Added data-fiber, which binds the messages object on the client side and the object inside the MESSAGES model on the server side.
  • We indicated that we need data-fiber for 3 things: load, observe, refresh. Load – for initial data loading, Observe – for synchronization with the server of changes on the client side, Refresh – for periodic data loading from the server side.

At startup, you can even chat with some other visitor to your guest page

GitHub commit to help

Server methods

In the previous chapter, I cheated a little by granting read / write access to the message collection to all site visitors at once. It is highly discouraged to do this, because then, through data-fiber, any sender can overwrite all messages on the server with something of his own or even erase them. Data-fibers should only be used to link custom browser-side objects to server-side data objects belonging to the same user. This means no static models or data!

How can we fix the situation? For this we have to:

  • Ditch data-fiber, which works across the board, and only use it to initially load the message list.
  • Use the server side method of adding a new message to the list.

Thus, everyone can only add new messages to the general list, but they can neither delete nor change existing ones. Let’s also complicate the server side a little: we will save not just JSON received from the client, but a specialized Message class with the necessary fields, methods, etc. to work with data on the server side.

Let’s start with a Message class for storing user messages. This might be some JPA class, by the way, that allows you to save data to a database.

public class Message implements IClusterable {
	@JsonProperty("message")
	private String text;
	private Date date;
	
	public String getText() {
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
	public Date getDate() {
		return date;
	}
	public void setDate(Date date) {
		this.date = date;
	}	
}

Pay attention to @JsonProperty. Thus, we have redirected the JSON field “message” to our Java field “text”.

Next, let’s change ChatApp.java to do what is described above: add a vuecket method to save the message. Also in the code, you can notice the cutting of the list of messages to only 20 (Habr users are very diligent), but when you delete a message, it is still saved forever in the server logs.

@VueFile("ChatApp.vue")
public class ChatApp extends VueComponent {
	
	private static final Logger LOG = LoggerFactory.getLogger(ChatApp.class);
	private static final int MAX_SIZE = 20;
	private static final IModel> MESSAGES = Model.ofList(new ArrayList());

	public ChatApp(String id) {
		super(id);
		addDataFiber("messages", MESSAGES, true, false, false);
	}
	
	@VueMethod
	public synchronized void addMessage(Context ctx, Message message) {
		List list = MESSAGES.getObject();
		list.add(message);
		trimToSize(list, MAX_SIZE);
		IVuecketMethod.pushDataPatch(ctx, "messages", list);
	}
	
	private void trimToSize(List list, int size) {
		//It's OK to delete one by one because in most of cases we will delete just one record
		while(list.size()>size) LOG.info("Bay-bay message: {}", list.remove(0));
	}
}

See the @VueMethod annotation method? In it, we receive a new message, save it in the list, cut it and send the already updated list to the client. Also note that data-fiber has been reconfigured to only request data when the Vue application boots up.

We also need to change the logic in ChatApp.vue so that instead of the local “messages” field, send a new message to the server in asynchronous mode (vcInvoke)

module.exports = {
    data: function() {
        return {
            text : "",
            messages: []
        }
    },
    methods: {
    	addMessage : function() {
    		this.vcInvoke("addMessage", {
    			message: this.text,
    			date: new Date()
    		});
    		this.text = "";
    	}
    }
}

What we learned from the chapter:

  • How to Create Server Side Methods for Vuecket
  • How to call methods on the server from a browser
  • How to send the necessary changes to the client

For a law-abiding visitor, nothing has changed, but a hacker can no longer so easily change the general list of messages on the server

GitHub commit to help

Conclusion

On this today, let me round off. There are many more improvements that can be made to our code, but I’ll leave that to you, dear readers. If you need help, write, we will help!

Do you like the framework? Please share your opinion. After all, it is your opinion that Open Source lives and develops.

Spoiler of upcoming Vuecket improvements

  • WebSocket support for more reactive client-server communication.
  • The ability to send the delta of changes between the browser and the server, and not the entire object as a whole.
  • More settings for data-fibers to fine tune them.
  • A set of Vuecket / Wicket components that are already pre-integrated with the most interesting VueJS libraries, for example, the already mentioned component for displaying text in Markdown

Similar Posts

One Comment

  1. Not worked! Error text:
    [WARNING] The POM for org.orienteer.vuecket:vuecket:jar:1.0-SNAPSHOT is missing, no dependency information available

Leave a Reply