eScriptorium Frontend development
This documentation aims to explain the new architecture of the Javascript side of the eScriptorium application. It also provides several tips to follow when adding further developments.
The frontend is divided in three parts:
- vendored libraries, pulled from NPM registry
- Javascript code directly used by the Django application (jQuery helpers, some libraries initialisation, ...)
- the eScriptorium editor, built with Vue.js
front
folder structure
The front
folder of eScriptorium includes several folders/files. Among them are .css
files, .js
files allowing the application to run, .vue
files detailing the Single-File implementation of the Vue.js components, and various NPM and webpack configuration files.
-
css ➔ Folder containing
.css
files used to style the Vue.js application. -
dist
NOT COMMITTED➔ Folder containing the compressed archives of the Vue.js code (3 chunks,vendor
,main
andeditor
) that will be used by the Django application to integrate it. -
node_modules
NOT COMMITTED➔ Folder acting like a cache for the external modules that the Vue.js part depends on. -
src ➔ Folder containing several required
.js
files to run the Vue.js part of eScriptorium.-
editor ➔ Folder containing the core of the Vue.js part with notably:
-
main.js
: the entry point of the application, -
api.js
: the list of API calls, - the implementation of the VueX store in
index.js
and under thestore
folder.
-
-
editor ➔ Folder containing the core of the Vue.js part with notably:
-
vue/components ➔ Folder gathering all the
.vue
Single File Components of the Vue.js part of eScriptorium. -
package-lock.json
➔ File describing a single representation of the project dependency tree. -
package.json
➔ File listing all Vue.js application dependencies. -
webpack.common.js
➔ File detailing the common configuration to serve the application code usingwebpack
. -
webpack.dev.js
➔ File detailing the specific dev configuration to serve the application code usingwebpack
. -
webpack.prod.js
➔ File detailing the specific production configuration to serve the application code usingwebpack
.
Setup
front
folder.
How to install
We use NPM to handle project dependencies.
To install vendored libraries used by the application and generate the node_modules folder, you have to run once this command:
npm install
Once the dependencies are installed, you can choose one of the following build to serve JS libraries and the Vue application code.
- Either generate a single JS build in development mode, it will enable warnings/errors display in the browser console:
npm run build
- Or generate a continuous JS build using the
--watch
option and in development mode, it will enable warnings/errors display in the browser console. This build is particularly useful when developing since it will automatically reload and re-serve bundles after every file change:
npm run start
- Or generate an optimized production build:
npm run production
Vendored libraries
Vendored libraries are now provided by npm
package manager and not anymore included directly as .js
files in the application.
npm install
as described below.
Third party dependencies are listed in front/package.json
file and installed through npm install
command that will locally generate the front/node_modules
folder.
🔍 It's important not to commit vendored libraries and instead just rely on thenpm
configuration for their installation. This allows us to clearly view which package at which version is needed.
How to add a new dependency
To add a new library, you will have to find your package on the npm registry.
Then you will be able to run:
npm install --save your-package@version
The dependency will be directly added to the package.json
file and cached in the node_modules
folder.
To serve your new package to eScriptorium, you'll then have to import it in the front/src/vendor.js
file.
Please commit changes to package.json
and package-lock.json
in a merge request.
🔍 For further advice regarding dependencies installation, you can refer to the official NPM documentation.
Webpack chunks
We use Webpack to serve the Vue.js application code and vendored libraries as compressed bundles.
Webpack allows us to build three chunks to represent the logical separation between each part:
vendor
chunk
The vendor
chunk is used to serve the vendored libraries of the application. Its entrypoint is represented by the file front/src/vendor.js
where every useful dependency is imported.
main
chunk
The main
chunk is used to serve the .css
files and every eScriptorium .js
files that are not directly included in the Vue.js application core but still used by the Django application. Its entrypoint is represented by the file front/src/main.js
where each style and script files are imported.
editor
chunk
The editor
chunk is used to serve the Vue.js application code including VueX store and Vue.js components. This application is displayed when editing a DocumentPart
on eScriptorium. Its entrypoint is represented by the file front/src/editor/main.js
where the Editor
component encompassing the whole app and the store are imported.
Vue.js application architecture
Components
graph TD;
Editor-->ExtraInfo;
Editor-->TabContent;
Editor-->ExtraNav;
TabContent-->VisuPanel;
VisuPanel -...-> BasePanel([BasePanel mixin]);
TabContent-->SourcePanel;
SourcePanel -...-> BasePanel([BasePanel mixin]);
TabContent-->DiploPanel;
DiploPanel -...-> BasePanel([BasePanel mixin]);
TabContent-->SegPanel;
SegPanel -...-> BasePanel([BasePanel mixin]);
VisuPanel-->VisuLine;
VisuLine -...-> LineBase([LineBase mixin]);
VisuPanel-->TranscriptionModal;
DiploPanel-->DiploLine;
DiploLine -...-> LineBase([LineBase mixin]);
SegPanel-->SegLine;
SegPanel-->SegRegion;
SegPanel-->Help;
TranscriptionModal-->LineVersion;
TranscriptionModal-->HelpVersions;
TranscriptionModal-->HelpCompareTranscriptions;
Single-file components
Each component (.vue
file) is built following the Single-File Component implementation (SFC).
Those files are composed of 3 parts:
- the
<template/>
one, containing the HTML code for the component, - the
<script/>
one, containing its logic, - and finally, the
<style/>
one, containing its scoped CSS style.
It allows us to obtain more cohesive and maintainable components since their template, logic and style are inherently coupled.
VueX store
The VueX store is divided in 5 sub-modules.
graph TD;
index.js-->document.js;
index.js-->lines.js;
index.js-->parts.js;
index.js-->regions.js;
index.js-->transcriptions.js;
In each of those modules we can find various state attributes, mutations and actions related to their main focus.
The store is shared by the whole Vue.js application, it means that every component can access the store state at any time and alter it by calling actions and mutations.
🔍 Follow this link to learn more about the state management pattern.
Communicate through the store
As we said above, components can easily communicate using the store to share information.
Reading the store
You can retrieve a state attribute from the store using this.$store.state.yourmodule.yourattribute
in your component. DO NOT assign this reference with a new value, use actions and mutations only to do so!
For example, to read the id
state attribute from the document.js
store module, we can use this line in a Vue.js component: this.$store.state.document.id
.
🔍 Find more about this topic in the VueX official documentation on store state.
Calling actions
To make API calls and/or initiate an alteration of the store state, you should use VueX actions. You can call an action using this.$store.dispatch('yourmodule/youraction', yourpayload)
in your component.
For example, to retrieve the Document
and populate the shared store with its information, we can call this line in a Vue.js component: this.$store.dispatch('document/fetchDocument', documentId)
. This action will call an API endpoint listed in the api.js
file and populate various store attributes with new values using mutations.
🔍 Find more about this topic in the VueX official documentation on store actions.
Altering store state
To properly alter the store state, you must use VueX mutations. You can call a mutation using this.$store.commit('yourmodule/yourmutation', payload)
in your component, but note that it's better to call actions that will perform the mutations internally using commit()
.
For example, to modify the value of the stored documentId
, we can call this line in a Vue.js component: this.$store.commit('document/setId', newId)
. This will alter the store state and changes will be repercuted on all components since they all share the same store instance.
🔍 Find more about this topic in the VueX official documentation on store mutations.
Best practices
- Use the store to communicate between components:
- Avoid passing props, handle them in a store module.
- Avoid using events (
$emit
,$on
), prefer using store actions.
- Always add a new API call in
front/src/api.js
. - Avoid calling mutations (with
this.$store.commit(...)
) directly in components, prefer to call actions altering the store state through commits instead (withthis.$store.dispatch(...)
). - Try to implement reusable/configurable components (e.g: a generic
Modal
component where you can set its title, its content, etc).
🔍 See also Vue.js Style Guide.