15 KiB
Web application
Description
The WebApp is the default user interface of the ReCodEx project. It is a Single Page Application (SPA) which users access through their web browsers. This user interface allows students to join groups corresponding to their school classes, submit new solutions and see the results as soon as the solution is evaluated by the backend. Teachers can manage their groups by adding and removing students, assigning exercises and examining the automatically evaluated solutions of their students and their total score gained throughout the semester.
The actions performed by the user are sent to the API server over the HTTPS protocol and the web application does not modify the database or access files on ther server directly and all the inputs sent from the web application are additionally validated on the API server.
The WebApp is written in ECMAScript (often referred to as JavaScript) and HTML5. The React library developed by Facebook is used to manage the user interface and the Redux framework created by Dan Abramov is used to manage the application state. Both of these frameworks are industry standards used in production by many companies worldwide. Many other smaller libraries and components are used, notably the Immutable.js and Format.js by Yahoo via redact-intl integration library.
Multiplatform approach
Since the runtime environment of WebApp is user's web browser it does not need any installation on enduser's device and does not require any particular platform or non-standard software installed on user's device. This application is designed and implemented in a way which should be suitable for modern web browsers on both desktop and mobile devices.
Architecture
The whole project is written using the next generation of JavaScript referred to as ECMAScript 6 (also known as ES6, ES.next, or Harmony). Since not all the features introduced in this standard are implemented in today's modern web browsers (like classes and spread operators) and hardly any are implemented in older versions of web browsers which are currently still in use, the source code is transpiled into the older standard ES5 using Babel.js transpiler and bundled into a single script file using the webpack moudle bundler. The need for a transpiler also arises from the usage of the JSX syntax for declaring React components. To read more about these these tools and their usage please refer to the installation section. The whole bundling process takes place at deployment and is not repeated afterwards.
Redux middleware and helpers
- @todo: apiMiddleware
- @todo: resourceManager
Declarative UI components
The implementation of WebApp is split across more than a hundred small components which are composed together. There are two basic types of React components - presentational and stateful components.
-
Presentational components are in fact pure functions which yield their output based solely on the input props and application's context (e. g. current language has effect on URLs). These components are preferred and should be as simple as possible. These components are placed in the subfolders of
src/components
directory. -
Stateful components are connected to the redux store and yield information based on the either the props and context, but also on the current state of the application. These components are more complicated and often make use of React's components' life cycle methods. Stateful components are often referred to as containers and the source code files are placed in a separate directories called
src/containers
andsrc/pages
.- There is no technical difference between containers and pages. Pages are used as "root" components for different pages and are registered in the app's router.
As was mentioned earlier in the text, there is over a hundred components in the whole application and most of them are very simple. Some of the most important and widely used components and described in the following paragraphs.
App
App container is meant to be the root of the react-router tree. It checks whether a user is logged in and loads his profile and settings from the API. App container also handles access token refreshing.
LayoutContainer
LayoutContainer handles the dependency injection of the localized links based on the current language stated in the URL. It also controls the state of the sidebar - collapsing and showing the sidebar.
Layout, Header, Sidebar, Footer
The Layout, Header, Header, and Sidebar components hold the main HTML structure of the page which is displayed to the user and hold links to different parts of the application.
PageContent
The PageContent component holds the main content of a page with the common structure for all pages - the title, description, breadcrumbs,, content. The component passes the title and description to the Helmet library which reflects these into the <head>
section of the HTML document.
ResourceRenderer
ResourceRenderer component is given a resource managed by the resourceManager as a prop and displays different content based on the state of the given resource - still loading, loading failed, fully loaded.
Passing content for the loading and failed states though props is optional; however, the content for the loaded state is required and must be passed as a child to the ResourceManager
.
Multiple resources can be passed as an array to the component and it will wait in the loading state until some of the resources are still loading. If one of the resources fails to load the component will switch to the failed state. When all the files are fully loaded then the component displays the content for the loaded state.
It is worth mentioning that the component is completely stateless and "switching of the states" is the effect of several renderings of the component over time.
Page
Page combines the two previously described components, since they are often used together for displaying a page content which is dependant on a resource.
CommentThreadContainer
CommentThreadContainer can be used on any page of the webapp to create a discussion thread. A unique identification must be assigned to every thread so the comments in the distinct threads do not interleave.
Bootstrap and AdminLTE theme
The UI of the application is built using the stylesheets from the Bootstrap framework from Twitter and the AdminLTE theme from Abdullah Almsaeed. None of the JavaScript plugins from these libraries is used.
A package react-bootstrap is used. Components specific to the AdminLTE theme are implemented and stored in the src/components/AdminLTE
folder and its subfolders.
Box, FormBox
Box is a frequently used component for bounding other components like text or tables inside. It is a re-styled Panel
component from Bootstrap. It can be collapsable and can be displayed in different colors and types. FormBox is just o wrapper of Box
adjusted for holding forms inside of the body.
CommentThread
CommentThread component is used directly by the CommentThreadContainer
described earlier and uses the HTML markup introduced by the theme author to achieve nice comment threads.
Avatar, LoadingAvatar, FailedAvatar
Avatar component is used on many places for displaying a round profile picture of a user. LoadingAvatar
and FailedAvatar
are used to mock the visual appearance of the avatar while the image is being downloaded or if the download failed for some reason.
Icons
The free FontAwesome icon pack is used in the application via the react-fontawesome library. Many types of icons are defined as components (e. g. LoadingIcon
, WarningIcon
, SuccessIcon
, DeleteIcon
, and many others) to be used throughout the application so the same symbols are used for the same purposes.
Forms
The redux-form library is used for the management of forms' states. Several of the implemented components can be used as form fields, namely the CheckboxField, MarkdownTextAreaField, SourceCodeField, DatetimeField, TabbedArrayField, LanguageSelectField, and a few others.
Localization and globalization
The whole application is prepared for localization. All the text can be exported from the user interface and translated into several languages. The numbers, dates, and time values are also formatted with respect to the selected language. The react-intl and Moment.js libraries are used to achieve this.
All the strings can be extracted from the application using a command:
$ npm run exportStrings
This will create JSON files with the exported strings for the 'en' and 'cs' locale. If you want to export strings for more languages, you must edit the /manageTranslations.js
script. The exported strings are placed in the /src/locales
directory.
Installation
Web application requires NodeJS server as its runtime environment. This runtime is needed for executing JavaScript code on server and sending the pre-render parts of pages to clients, so the final rendering in browsers is a lot quicker and the page is accessible to search engines for indexing.
But some functionality is better in other full fledged web servers like Apache or Nginx, so the common practice is to use a tandem of both. NodeJS takes care of basic functionality of the app while the other server (Apache) is set as reverse proxy and providing additional functionality like SSL encryption, load balancing or caching of static files. The recommended setup contains both NodeJS and one of Apache and Nginx web servers for the reasons discussed above.
Stable versions of 4th and 6th series of NodeJS server are sufficient, using at least 6th series is highly recommended. Please check the most recent version of the packages in your distribution's repositories, there are often outdated ones. However, there are some third party repositories for all main Linux distributions.
The app depends on several libraries and components, all of them are listed in package.json
file in source repository. For managing dependencies is used node package manager (npm
), which can come with NodeJS installation otherwise can be installed separately. To fetch and install all dependencies run:
$ npm install
For easy production usage there is an additional package for managing NodeJS processes, pm2
. This tool can run your application as a daemon, monitor occupied resources, gather logs and provide simple console interface for managing app's state. To install it globally into your system run:
# npm install pm2 -g
Configuration and usage
The application can be run in two modes, development and production. Development mode uses only client rendering and tracks code changes with rebuilds of the application in real time. In production mode the compilation (transpile to ES5 standard using Babel and bundle into single file using webpack) has to be done separately prior to running. The scripts for compilation are provided as additional npm
commands.
- Development mode can be use for local testing of the app. This mode uses webpack dev server, so all code runs on a client, there is no server side rendering available. Starting is simple command, default address is http://localhost:8080.
$ npm run dev
- Production mode is mostly used on the servers. It provides all features such as server side rendering. This can be run via:
$ npm run build
$ npm start
Both modes can be configured to use different ports or set base address of used API server. This can be configured in .env
file in root of the repository. There is .env-sample
file which can be just copied and altered.
The production mode can be run also as a demon controled by pm2
tool. First the web application has to be built and then the server javascript file can run as a daemon.
$ npm run build
$ pm2 start bin/server.js
The pm2
tool has several options, most notably status, stop, restart and logs. Further description is available on project website.
Configurable items
Description of configurable options. Bold are required values, optional ones are in italics.
- NODE_ENV -- mode of the server
- API_BASE -- base address of API server, including port and API version
- PORT -- port where the app is listening
- WEBPACK_DEV_SERVER_PORT -- port for webpack dev server when running in development mode. Default one is 8081, this option might be useful when this port is necessary for some other service.
Example configuration file
NODE_ENV=production
API_BASE=https://recodex.projekty.ms.mff.cuni.cz:4000/v1
PORT=8080