frontend/docs/models-services.md
kolaente befa6f27bb feat: rename list to project everywhere
fix: project table view

fix: e2e tests

fix: typo in readme

fix: list view route

fix: don't wait until background is loaded for list to show

fix: rename component imports

fix: lint

fix: parse task text

fix: use list card grid

fix: use correct class names

fix: i18n keys

fix: load project

fix: task overview

fix: list view spacing

fix: find project

fix: setLoading when updating a project

fix: loading saved filter

fix: project store loading

fix: color picker import

fix: cypress tests

feat: migrate old list settings

chore: add const for project settings

fix: wrong projecten rename from lists

chore: rename unused variable

fix: editor list

fix: shortcut list class name

fix: pagination list class name

fix: notifications list class name

fix: list view variable name

chore: clarify comment

fix: i18n keys

fix: router imports

fix: comment

chore: remove debugging leftover

fix: remove duplicate variables

fix: change comment

fix: list view variable name

fix: list view css class name

fix: list item property name

fix: name update tasks function correctly

fix: update comment

fix: project create route

fix: list view class names

fix: list view component name

fix: result list class name

fix: animation class list name

fix: change debug log

fix: revert a few navigation changes

fix: use @ for imports of all views

fix: rename link share list class

fix: remove unused css class

fix: dynamically import project components again
2023-03-14 14:04:23 +00:00

5.3 KiB

Models and services

The architecture of this web app is in general divided in two parts: Models and services.

Services handle all "raw" requests, models contain data and methods to work with it.

A service takes (in most cases) a model and returns one.

Table of Contents

Services

Services are located in src/services.

All services must inherit AbstractService which holds most of the methods.

A basic service can look like this:

import AbstractService from './abstractService'
import ProjectModel from '../models/project'

export default class ProjectService extends AbstractService {
	constructor() {
		super({
			getAll: '/projects',
			get: '/projects/{id}',
			create: '/namespaces/{namespaceID}/projects',
			update: '/projects/{id}',
			delete: '/projects/{id}',
		})
	}
	
	modelFactory(data) {
		return new ProjectModel(data)
	}
}

The constructor calls its parent constructor and provides the paths to make the requests. The parent constructor will take these and save them in the service. All paths are optional. Calling a method which doesn't have a path defined will fail.

The placeholder values in the urls are replaced with the contens of variables with the same name in the corresponding model (the one you pass to the functions).

Requests

Several request types are possible:

Name HTTP Method
get GET
getAll GET
create PUT
update POST
delete DELETE

Each method can take a model and optional url parameters as function parameters. With the exception of getAll(), a model is always mandatory while parameters are not.

Each method returns a promise, so you can access a request result like so:

service.getAll().then(result => {
	// Do something with result
})

The result is a ready-to-use model returned by the model factory.

Loading

Each service has a loading property, provided by AbstractModel. This property is a boolean, it is automatically set to true (with a 100ms delay to avoid flickering) once the request is started and set to false once the request is finished. You can use this to show and hide a loading animation in the frontend.

Factories

The modelFactory takes data, and returns a model. The result of all requests (with the exception of the delete method) is run through this factory. The factory should return the appropriate model, see models down below on how to handle data in models.

getAll() checks if the response is an array, if that's the case, it will run each entry in it through the modelFactory.

It is possible to define a different factory for each request. This is done by implementing a method called model{TYPE}Factory(data) in your service. As a fallback if the specific factory is not defined, modelFactory will be used.

Before Request

For each request exists a before{TYPE}(model) method. It recieves the model, can alter it and should return the modified version.

This is useful to make unix timestamps from javascript dates, for example.

After Request ?

There is no after{TYPE} method which would be called after a request is done. Processing raw api data should be done in the constructor of the model, see more on that below.

Models

Models are a bit simpler than services. They usually consist of a declaration of defaults and an optional constructor.

Models are located in src/models.

Each model should extend the AbstractModel. This handles the default value parsing.

A model does not handle any http requests, that's what services are for.

A simple model can look like this:

import AbstractModel from './abstractModel'
import TaskModel from './task'
import UserModel from './user'

export default class ProjectModel extends AbstractModel {
	
	constructor(data) {
		// The constructor of AbstractModel handles all the default parsing.
		super(data)
		
		// Make all tasks to task models
		this.tasks = this.tasks.map(t => {
			return new TaskModel(t)
		})
		
		this.owner = new UserModel(this.owner)
	}
	
	// Default attributes that define the "empty" state.
	defaults() {
		return {
			id: 0,
			title: '',
			description: '',
			owner: UserModel,
			tasks: [],
			namespaceID: 0,
			
			created: 0,
			updated: 0,
		}
	}
}

Default values

The defaults() functions provides all default values. The AbstractModel constructor will take all the data provided to it, and fill any non-existent, undefined or null value with the default provided by the function.

Constructor

The AbstractModel constructor handles all the default value parsing. In your model, the constructor can do additional parsing, like making js date object from unix timestamps or parsing the contents of a child-array into a model.

If the model does nothing like this, you don't need to define a constructor at all. The parent will handle it all.

Access to the data

After initializing a model, it is possible to access all properties via model.property. To make sure the property actually exists, provide it as a default.