Skip to content

Documentation

AlexanderSchl edited this page Jul 1, 2022 · 17 revisions

Frontend Rendering and Data Structure

This documentation aims to describe in detail what happens in the frontend, this is how data gets requested from the running backend, what checks it undergoes, how it is displayed to the user, and finally how it gets sent back to the backend, for instance for storing changes. This in done in hope of clarifying the code making up the page files of the frontend, while at the same time giving an understanding of the overall idea and paradigms that were followed during the project's development.

The core feature of the website is to let the user edit some form, and then upon saving the changes send this form, this is a data object representing it, to the backend to ultimately save it within a database. When the user enters the view for filling out a new form, the page first requires the backend to send it a predefined template, a data object that holds the default structure and contents, as well as the selectability and the type of fields to be displayed. This object, currently, originates from instantiating the class ProjectReportTemplate, which provides a field called groups. This variable is Map containing Group objects and Strings as keys to identify and access each group. A group in turn is itself a collection of fields, and a field is, from the frontend's perspective, ultimately an editable region in which a user can write text or select one of a given amount of options (among other kinds of fields). The initially loaded form is therefor a template of groups, each containing several fields. How exactly these groups are initialized and assembled can be seen in the DefaultProjectReportTemplate class of the backend, which defines several groups and then assigns them to an initially instantiated ProjectReportTemplate object.

erstellen.js is the page rendered when a user wants to fill out a new form. The page first calls getServerSideProps() which sends a fetch request to the running backend to receive the data representing the default template. Next this obtained data object is passed to the function HomePage() which iterates through the data and displays to the user. While this function doesn't alter the template in any way, it's iteration loops provide a good entry point for understanding the object's structure. The specific call is HomePage({allData}), where allData is the template object. Below is the main code of the function.

HomePage code

The map()-function is equivalent to an iteration over the object it is called upon. Here the call is groups.map(), where groups is an array. The map()-function iterates over all the array's values and alters them according to the given function parameter (map(function), for further understanding you may refer to the JavaScript reference). After iterating, map() returns a new array with the modified values, however leaving groups unchanged. groups itself is a property of allData and contains, as the name suggests, the data of a single group (fields and further attributes such as identifiers and flags). Right after it another iteration occurs, this time within a single group object, here named dataOut. Within this block several data gets extracted from dataOut which is used to assemble HTML-elements which ultimately make up the new form the user can edit. The picture below shows conceptually a full expansion of allData.

allData data structuring

In summary, a form consists of many groups, each of which consists of individual attributes and many fields, each of which consist of a number of indiv. attributes. Following a short overview about a field-object will be given, such to clarify the meaning of each property and how it is used.

  • number: a label of the field specifying its position and category (or subcategory) within the list of fields of the group. Ex.: "0", "1.a", "1.b"
  • name: like number, serves as a descriptor of the field
  • inputType: a string specifying the type of the field. There are a total of 4 different types being: 'SINGLE_INPUT', 'MULTI_INPUT', 'SELECT_SINGLE_INPUT' and 'SELECT_MULTI_INPUT'. The HomePage()-function selects and sets the corresponding HTML-field based on this attribute
  • type: specifies the input type of the data the field holds. Could be for ex. an integer, a string or a date
  • value: the value contained in the field (for 'SINGLE'-input-type fields)
  • values: an amount of values contained in the field (for 'MULTI'-input-type fields)
  • allowedValues: the field may only accept one the specified values (for instance given on 'SELECTION'-fields)
  • modifiers: holds META-information about the field, such as whether the field is required to be filled (to hold data) or not. Values include: "REQUIRED" and "AUTOGENERATED"

Each of these attributes is also carried over to the backend, where dedicated classes then access them to finally map the entire object to a DB-entry. On assembling HTML-SpecificComponent (this is the field) in HomePage(), one can notice there are calls such as OptionDecide(), DisDecide() (for "disable") and LabDecide() for how to display the field's label. Each of these function's accepts as a parameter a filed object. Then by reading the modifiers attribute the method decides for ex. whether the field is a "REQUIRED" field or not, based on which the field is then marked as such.

Form Submission to Backend

After HomePage() is run, the user can interact with the displayed field, this is, edit them. After filling up the form, the user clicks on the Speichern-(Save)-button, which triggers a call of fillUp(). The fillUp() method is responsible for applying the changes made by the user to the form. In particular this means that individual fields get update by the given value, ultimately altering the originally obtained template (some default values are updated with those passed or selected by the user). The following code snippet is an extract of fillUp() which showcases this procedure of updating values.

snippet fillup

Here allDataTmp again represents the originally obtained form template. name and id are the values obtained from the name-attribute of an individual field. Then the corresponding group the field belongs to is accessed through groups[name], and then within this group the corresponding field in question is accessed through field[id] (name and id uniquely identify the specific field). Now the inputType is identified to be 'MULTI_INPUT'. value in this specific case is an array of values. arrTmp then gets assigned the value object, and finally fields[id].values gets overwritten with arrTmp. One can also note the effective specific usage of .values inst. of .value, as this is the attribute specifically meant for 'MULTI'-input types. After updating all fields a window opens prompting the user to confirm or abort the saving process. If confirmed the method onSubmit() is called, which calls Post(). In Post(body) the entire altered template is converted to a JSON-String and sent back to the backend in form of a 'Project Report'. On the backend side ProjectReportSbMapper accepts the JSON-String and based on its contents, recreates the corresponding group and field objects. The final goal is to save the data received from the frontend persistently into a database. This is process is explained in more detail in the backend documentation.

Notes and Improvements

As of the current implementation, the frontend always sends back an entire form's worth of information back to the server. While this may be acceptable when the user creates a new form from scratch, the same procedure shouldn't be used when only updating an existing form. In this case it would be more appropriate to just send the changes instead of the entire document. One would have to implement additional logic and possibly alter the existing structure of a field object, such that the server can identify the change and the location at which this change occurred.
A further optimization, motivated mainly by the current implementation of the backend, is to remove the value attribute from a field and only leave values. values would then be used for 'MULTI'-input as well as for 'SINGLE'-input types, in which case it would only store 1 value. The reason for this is that currently the backend instantiates classes for each input type, when actually this distinction could be severely simplified by just looking at the inputType attribute. If such a change in code should take place, one would also have to change the corresponding code in the frontend (mainly in fillUp()), while this would likely result in a simplification of the code instead of complicating it.
Further it may also make sense to simplify the overall structure of a template's data. Currently as the above picture shows, the template data is nested several times, where one has to go through 'data > groups > fields > attr' to access a field's attribute. Instead a conceivable idea would be to reduce the nesting to 3 or 2 levels, even if that means to have more attributes on a single level.

Lastly an improvement that doesn't optimize a process or alter any behavior, but is rather improves coding style. Right now fillUp() gets called right after the user clicks "Speichern". After the "filling up" is done the user gets prompted to either confirm the action of saving, or decline it. Here it would be better if the user gets prompted first, and only after confirmation the actual fill up should be performed. The order of these events can be easily changed in fillUp().

The following sequence diagram shows the current processing of the template data.

structureOld

As you can see the current implementation uses the template object to fill out the form. After editing and saving the form the method "fillUp()" is called. Currently, this method iterates through the whole template data. However, the new backend structure allows us to specify and precisely change the required data. For instance, it is not required anymore to send the whole template object back to the backend when only a couple of values have been changed. Instead only the changed data is sent back to the backend. This leads us to a new way of sending back the data, by selecting the specific value that has been changed. This can be realized by creating an object which stores all the changed values. A way of implementation may look like this: in the method "fillUp()" instead of overwriting the changed value as is the case now, the value will be stored in a new array e.g. by pushing "allData > groups > fields > attr" into the new array. To check if a value has changed simply check if the template value doesn't equal the passed value anymore e.g. if groups.fields.value !== value. Subsequently, the new array will be sent back to the backend where it will be processed further.

Clone this wiki locally