mirror of https://kolaente.dev/vikunja/frontend
Move everything to models and services (#17)
parent
8559d8bb97
commit
9b0c842ae1
@ -0,0 +1,184 @@
|
||||
# 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)
|
||||
* [Requests](#requests)
|
||||
* [Loading](#loading)
|
||||
* [Factories](#factories)
|
||||
* [Before Request](#before-request)
|
||||
* [After Request?](#after-request-)
|
||||
* [Models](#models)
|
||||
* [Default Values](#default-values)
|
||||
* [Constructor](#constructor)
|
||||
* [Access Model Data](#access-to-the-data)
|
||||
|
||||
## 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:
|
||||
|
||||
```javascript
|
||||
import AbstractService from './abstractService'
|
||||
import ListModel from '../models/list'
|
||||
|
||||
export default class ListService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
getAll: '/lists',
|
||||
get: '/lists/{id}',
|
||||
create: '/namespaces/{namespaceID}/lists',
|
||||
update: '/lists/{id}',
|
||||
delete: '/lists/{id}',
|
||||
})
|
||||
}
|
||||
|
||||
modelFactory(data) {
|
||||
return new ListModel(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:
|
||||
|
||||
```javascript
|
||||
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](#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:
|
||||
|
||||
```javascript
|
||||
import AbstractModel from './abstractModel'
|
||||
import TaskModel from './task'
|
||||
import UserModel from './user'
|
||||
|
||||
export default class ListModel 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.
|