Etude du workflow des pages pages/organization/index.vue et pages/organization/address/*
La page intègre le component UiCollection, qui prend en paramètres :
L'UiCollection récupère la méthode getCollection de useDataUtils (à qui elle a passé le dataProvider)
Elle appelle ensuite cette méthode et récupère le fetchState et les items sous forme de ref
Enfin, elle passe les items à son slot, qui les utilise pour afficher les données
entity/new.vuePasse le $dataProvider à useDataUtils qui retourne la méthode createItem
Passe le store et le type d'entité à la méthode createItem qui retourne une fonction create et deux refs : loading (bool) et item (Model).
La méthode createItem commit aussi le type du formulaire (FORM_STATUS.CREATE), et met le loading à true
Côté client seulement: instancie une nouvelle Entité, et la passe à la méthode create qui la persist dans le repository
La page inclut ensuite un component FormXxxx correspondant au type d'entité.
Peut utiliser des composables pour retourner des données complémentaires (ex: listes déroulantes). Il récupère alors le dataProvider depuis le context pour le passer aux méthodes du composable.
Récupère le repo propre à l'entité via repositoryHelper.getRepository(Xxxxx)
Génère une query depuis le repo, déclare éventuellement les relations à charger au passage
Génère une méthode submitActions qui retourne une liste d'actions caractérisées par : type d'action (cf. SUBMIT_TYPE) et adresse à laquelle se rendre après l'action
Inclut le composant générique UiForm et lui passe l'id, le model, la query, les submitActions
Importe via des methodes de composables :
$dataPersister et storeroutermarkAsDirty, markAsNotDirty, readonly, nextStepFactoryCréé un store de type Page
Créé une ref null nommée form
Construit une computed ref entry qui sera passée au slot (le FormXxxx). Cette ref doit récupérer l'entrée du repo via queryHelper.getFlattenEntry(query.value, id.value)
Construit la méthode updateRepository qui sera aussi passée au slot. Cette méthode invoque la méthode markAsDirty() qui met à jour le store Form pour le déclarer en dirty, maj la valeur du champs de l'entité correspondante et persist le tout dans le repo.
Créé une méthode validate qui utilise la méthode du même nom du v-form (natif de vuetify)
Créé une méthode submit qui reçoit un argument next (type de l'action exécutée):
validate$dataPersister.invoke et récupère la réponsenext, qui est donc le type de l'action exécutéeCréé la méthode nextStep, qui appelles la méthode nextStepFactory de useForm. Cette méthode associe les types d'actions à des callbacks appellant des méthodes de useForm (save, sageAndGoto). Exécute ensuite le callback retourné. Ces deux callbacks ne font qu'appeller le router pour rediriger vers la page suivante.
Créé des méthodes showDialog et closeDialog qui mettent à jour le store générique
Créé enfin une méthode saveAndQuit qui :
submitquitForm (voir plus bas), mais sauf erreur, le router a déjà été appellé par la méthode submit...Lorsqu'on quitte le formulaire, la boite de dialogue s'affiche et propose trois options : back_to_form, save_and_quit, quit_form
Un clic sur le bouton back_to_form du formulaire appelle la méthode closeDialog qui ferme la boite de dialogue.
Un clic sur le bouton save_and_quit du formulaire appelle la méthode saveAndQuit (voir au dessus)
Un clic sur le bouton quit_form du formulaire appelle la méthode quitForm, qui à son tour:
Le formulaire reçoit de UiForm la ref entry et la méthode updateRepository
L'entry correspond à l'entité dans le store telle que récupérée via le repo
updateRepository sera appellée par chaque input qui sont mis à jour
entity/_id.vueIdem create, excepté que le formulaire est inclus dans une page entity/_id.vue
Passe le $dataProvider à useDataUtils qui retourne la méthode getItemToEdit
Parse l'id de l'entité à éditer directement depuis l'url
Passe l'id et le type d'entité à la méthode getItemToEdit qui appelle la méthode $dataProvider.invoke et retourne une ref fetchState.
GET
Exemple : l'affichage des adresses sur la fiche de la structure
La page intègre le component UiCollection, qui prend en paramètres :
L'UiCollection récupère la méthode getCollection de useDataUtils (à qui elle a passé le dataProvider)
Elle appelle ensuite cette méthode et récupère le fetchState et les items sous forme de ref
Enfin, elle passe les items à son slot, qui les utilise pour afficher les données
GET
Exemple : l'affichage du pays dans le détail des adresses de la fiche de la structure
Extrait l'id de l'Uri
utilise le useFetch natif de nuxt en lui passant en callback un appel à $dataProvider.invoke
L'item est ensuite retourné sous forme d'une ComputedRef qui retourne elle-même queryHelper.getItem
GET
Prend en paramètres :
Utilise useFetch en lui passant en callback un appel à $dataProvider.invoke(type: QUERY_TYPE.MODEL, ...)
GET
Récupère l'état des cotisations avec un useFetch auquel on passe en callback un appel à $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})
GET
Utilise useFetch en lui passant en callback un appel à $dataProvider.invoke(type: QUERY_TYPE.MODEL, ...)
GET
Utilise useFetch en lui passant en callback un appel à $dataProvider.invoke(type: QUERY_TYPE.MODEL, ...)
GET / EDIT
Propose une méthode pour maj le profile via $dataPersister.invoke({type: QUERY_TYPE.MODEL, ...})
DELETE
Appelle directement $dataDeleter.invoke
GET / EDIT
Récupère les notifs depuis l'api via un useFetch avec un callback à await $dataProvider.invoke({type: QUERY_TYPE.ENUM, enumType})
Récupère ensuite les notifs depuis le store sous forme de computed ref
GET
Récupère l'enum via un useFetch avec un callback à await $dataProvider.invoke({type: QUERY_TYPE.ENUM, enumType})
Lorsque les notifications ont été lues, créé un nouvel objet NotificationUser en :
repositoryHelper.persist$dataPersister.invoke(type: QUERY_TYPE.MODEL, ...)GET
Récupère l'image grâce à la méthode getOne du composable useImageProvider (à qui on a passé le data provider et la config du context)
getOne appelle un dataProvider avec le type QUERY_TYPE.IMAGE et lui passe les arguments de config de l'image (id, hauteur, largeur)
En cas d'erreur, retourne une image par défaut.
GET
Si un id a été passé aux props, récupère l'objet File via un useFetch avec un
callback à await $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, url: 'api/files', id: props.existingImageId })
On récupère ensuite l'image grâce à la méthode getOne du composable useImageProvider (à qui on a passé le data provider et la config du context)
getOne appelle un dataProvider avec le type QUERY_TYPE.IMAGE et lui passe les arguments de config de l'image (id, hauteur, largeur)
En cas d'erreur, retourne une image par défaut.
GET / PUT
Met à jour le store avec repositoryHelper.persist
Envoie la requête de maj du File avec $dataPersister.invoke(type: QUERY_TYPE.MODEL, ...)
POST
Créé un nouvel objet File à sauvegarder
L'envoie à l'API avec $dataPersister.invoke(type: QUERY_TYPE.FILE, ...)
Appelle useFetch en lui passant le callback $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})
Appelle useFetch en lui passant le callback $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})
Appelle la route /api/gps-coordinate-searching via un appel à $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})
Appelle la route /api/access_people via un appel à $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})
Appelle la route https://api-adresse.data.gouv.fr/search/... via un appel à $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})
Appelle la route /api/siret-checking via un appel à $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})
ou
Appelle la route /api/subdomains via un appel à $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})
Appelle la route /api/dolibarr/account/ via un appel à $dataProvider.invoke({type: QUERY_TYPE.DEFAULT, ...})
x faire un store 'appState', renommer form en formState? renommer page en pageState? >> oui, renommer les stores form, page, ... en formStore, pageStore, ... x utiliser des interfaces pour le state des stores : https://pinia.vuejs.org/core-concepts/state.html#typescript (déja fait a priori) >> déjà fait x j'ai l'impression que les dialogs ne sont pas gérés depuis le store "page", ce serait ptêt pas mal? >> NA
x utiliser les hooks pinia pour répercuter les maj sur l'api? >> https://pinia-orm.codedredd.de/guide/model/lifecycle-hooks >> créer nos propres hooks, mais s'inspirer des hooks pinia-orm
x je pense qu'on devrait sortir le $nuxt.$loading.start() du service baseDataManager, c'est pas trop à sa place ici >> plus d'actualité dans nuxt 3
x le data deleter ne passe pas par un dataProcessor pour mettre à jour le store. mais est-ce nécessaire au final? >> plus d'actualité dans nuxt 3
x est-ce qu'on devrait pas choisir soit de récupérer les données dans des composables (ex: useTypeOfPracticeProvider, useCountryProvider), soit dans les components? si on maintient les composables, on pourrait
les mettre dans un sous-dossier ? ou même factoriser la méthode ? >> plus d'actualité
x DataPersister L25 : c'est bizarre de stocker l'objet sérialisé dans une de ses propres props non ? on devrait peut-être créer une nouvelle variable et laisser là l'objet DataPersisterArgs
x ce serait pas mal de passer juste les props nécessaire aux différentes méthodes plutôt que le paquet queryArgs à chaque fois, je pense qu'on y verrait plus clair sur qui fait quoi
x Les services data managers devraient être indépendant de VuexOrm/PiniaOrm, on devrait donc remplacer le param 'query' du data persister par des données sérialisables
x passer le create et le loader de la page Address/new dans le formulaire? faire la même chose partout?
xconst entryCopy = query.value.first() dabs useForm (L185): on pourrait peut-être factoriser ça dans un composable? au passage, v-form a une méthode reset, ça pourrait ptêt faire le job?
x UiForm, methode saveAndQuit: est-ce qu'on passe dans la méthode quitForm au final? puisque la méthode nextStep appellée depuis submit a déjà dû faire la redirection?
x page address/_id, ligne 18 : on ne pourrait pas faire mieux pour le const id = parseInt(route.value.params.id)? faire ça en amont? dans un service? ou carrément parser les paramètres directement depuis un middleware et les stocker dans un store dédié en lecture seule?
x plutôt que d'instancier une entité dans la page et de la passer au form, est-ce qu'on ne passerait pas un param 'create' au form? il faudrait lever des erreurs si create == true & id != null ou create == false & id == null
x comment imposer des propriétés à tous les FormXxxx? Serait-il possible de mettre en place une interface pour l'array props, et de la tester depuis UiForm?
x useError, ligne 7 : je suppose que la docstring est pas la bonne?
x uiForm, L185-188 : je passerais ça dans une méthode resetEntry par ex
x components/Form/Organization/ContactPoint.vue, L62 : est-ce que l'action Back serait pas à sa place avec les actions SUBMIT_TYPE ?
x actions[SUBMIT_TYPE.SAVE] = { path:/organization/address/} : un moyen de rendre plus explicite que l'url dans path est la page où l'on se rend après l'action?
x est-ce que les chemins où se rendre après les actions submit ne devraient pas être paramétrables dans les props du formulaire lui-même?
x plutôt que de passer un param 'accordion' ({ path:/organization, query: { accordion: 'address_postal' } }), est-ce qu'on ne pourrait pas utiliser des anchors?
x voir à sortir les services profiles/* et rights/* et à les passer dans les composables (il faudra maj les plugins ability et castl)
x UiItemFromUri, ligne 52 : pqoi ne pas appeller directement ModelsUtils.extractIdFromUri(uri)
x UiInputImage, ligne 112 : est-ce que ce ne serait pas à sa place dans un processor attaché au data provider QUERY_TYPE.IMAGE ça?
x pourquoi avoir séparé les pages parameters et organization en deux? devrais-je faire la même chose avec subscription? (ex: pages/organization.vue et pages/parameters/index.vue)
getItemToEdit en getItem?createItem en getItemCreator ou un truc du genre, pour expliciter le fait qu'on retourne une méthode, pas qu'on créé directement l'objet
x page communication : passer getCurrentWebsite dans un service? (s'il n'existe pas déjà)
x UiCollection : on pourrait pas passer l'entry root plutot que son model+id? est-ce que le terme de parent serait plus précis ou pas ?
x middleware/auth.ts : on devrait mettre l'uri du front "admin" non?newLink en qqchose comme pageNewUri, ou linkToNewPage, ou même linkToNew, mais bon, c'est ptêt pas nécessaire...Mettre en place un "guichet unique" pour les entités, nommé par ex entityManager
Décliner les data-managers en :
em)enumManager)fileManager)imageManager), utile? ou fusionner avec le FileManager?Ces managers auraient des méthodes comme : getRepository (voir si nécessaire de garder l'étape des repos), fetch, persist, delete, new ou init, flush (?), rollback ou reset
Ils seraient responsables de la validation, de l'état de loading, de l'état dirty
Ils seraient importables depuis un composable du type useDataManager ou useEntityManager
Incorporer les méthodes de useDataUtils dans ce manager Voir si utile de récupérer des méthodes de useForm? Pas sûr, mais l'objectif serait que useForm soit seul à interagir avec le store formState, et aussi le seul à interagir avec.
L'idée serait que ce que l'on écrit comme ça aujourd'hui :
const {getItemToEdit} = useDataUtils($dataProvider)
const {fetchState} = getItemToEdit(id, Parameters)
const repository: VuexRepository<Model> = repositoryHelper.getRepository(Parameters)
const query: ComputedRef<Query> = computed(() => repository.query())
const entry: ComputedRef<AnyJson> = computed(() => {
return queryHelper.getFlattenEntry(query.value, id)
})
Puisse s'écrire plus ou moins comme ça :
const { em } = useEntityManager()
{ fetchState, entity } = em.fetch(Parameters, id) // où fetchState et entity serait des 'ref'?
Ou même quelque chose comme ça ?
const { em } = useEntityManager()
let fetchState = ref(true)
let parameters = ref(null)
em.fetch(Parameters, id).onSuccess((e) => { fetchState.value = false; entity.value = e; })
Si on décide finalement de conserver la mécanique actuelle, je propose alors de revoir les termes employés, afin de bien identifier les différentes opérations
Actuellement, cette mise à jour se fait dans store/index.js :
async nuxtServerInit ({ dispatch }, { req }) {
await dispatch('initCookies', { req })
await dispatch('getUserProfile')
},
async getUserProfile ({ dispatch }) {
const myProfile = await this.app.context.$dataProvider.invoke({
type: QUERY_TYPE.DEFAULT,
url: '/api/my_profile'
})
await dispatch('profile/access/setProfile', myProfile.data)
}
Ici a priori, un simple appel à Connection.get pourrait suffire (il faut juste ajouter une méthode get pour les
url custom, parce que getItem est dédié aux entities). D'autant que le denormalizer des data provider est le denormalizer
Hydra, ce qui n'est pas adapté aux routes comme api/my_profile par ex.
Ça donnerait un truc du genre :
async getUserProfile ({ dispatch }) {
const myProfile = await this.app.context.$connection.get('/api/my_profile')
await dispatch('profile/access/setProfile', myProfile)
}
Exemple de pages/organization.vue :
const {store, $dataProvider} = useContext()
const {getItemToEdit} = useDataUtils($dataProvider)
const id = store.state.profile.organization.id
const {fetchState} = getItemToEdit(id, Organization)
const repository = repositoryHelper.getRepository(Organization)
const query: ComputedRef<Query> = computed(() => repository.query())
const entry: ComputedRef<Item> = computed(() => {
return queryHelper.getItem(query.value, id)
})
return {
entry,
fetchState
}
Est-ce qu'on peut espérer quelque chose de ce genre ?
const { em } = useEntityManager()
const { fetchState, organization } = em.fetch(Organization, id)
return {
organization,
fetchState
}
Exemple de pages/organization/index.vue :
<script>
function getRepositories () {
return {
(...)
contactPointRepository: repositoryHelper.getRepository(ContactPoint),
(...)
}
}
setup () {
(...)
return {
repositories: () => getRepositories(),
(...)
}
}
</script>
<template>
<UiCollection
:query="repositories().contactPointRepository.query()"
:root-model="models().Organization"
:root-id="id"
:model="models().ContactPoint"
newLink="/organization/contact_points/new"
>
<template #list.item="{items}">
(...)
</template>
</UiCollection>
</template>
Dans components/Ui/Collection.vue :
const { rootModel, rootId, model, query }: ToRefs = toRefs(props)
const { $dataProvider } = useContext()
const { getCollection } = useDataUtils($dataProvider)
const {fetchState} = getCollection(model.value, rootModel.value, rootId.value)
const items: ComputedRef<Collection> = computed(() => queryHelper.getCollection(query.value))
return {
items,
fetchState
}
Est-ce qu'on peut espérer quelque chose de ce genre ?
const { em } = useEntityManager()
const { fetchState, contactPoints } = em.fetchBy(ContactPoint, Organization, organization_id)
return {
contactPoints,
fetchState
}
Exemple de pages/organization/contact_points/_id.vue :
<script>
setup () {
const {$dataProvider, route} = useContext()
const {getItemToEdit} = useDataUtils($dataProvider)
const id = parseInt(route.value.params.id)
const {fetchState} = getItemToEdit(id, ContactPoint)
return {
id,
fetchState
}
</script>
<template>
<FormOrganizationContactPoint :id="id" v-if="!fetchState.pending"></FormOrganizationContactPoint>
</template>
Dans components/Form/Organization/ContactPoint.vue :
<script>
setup () {
const repository: VuexRepository<Model> = repositoryHelper.getRepository(ContactPoint)
const query: Query = repository.query()
(...)
return {
model: ContactPoint,
query: () => query,
(...)
}
}
</script>
<template>
<UiForm
:id="id"
:model="model"
:query="query()"
:submitActions="submitActions">
>
<template #form.input="{entry, updateRepository}">
(...)
</template>
</UiForm>
</template>
Dans components/Ui/Form.vue :
<script>
setup () {
const {$dataPersister, store, app: {i18n}} = useContext()
const {id, query}: ToRefs = toRefs(props)
const entry: ComputedRef<AnyJson> = computed(() => {
return queryHelper.getFlattenEntry(query.value, id.value)
})
(...)
const submit = async (next: string|null = null) => {
if(await validate()){
markAsNotDirty()
try {
const response = await $dataPersister.invoke({
type: QUERY_TYPE.MODEL,
model: props.model,
id: store.state.form.formStatus === FORM_STATUS.EDIT ? id.value : null,
idTemp: store.state.form.formStatus === FORM_STATUS.CREATE ? id.value : null,
query: props.query
})
(...)
}
const quitForm = () => {
// reset entity
const entryCopy = query.value.first()
if (entryCopy && entryCopy.$getAttributes().originalState) {
repositoryHelper.persist(props.model, entryCopy.$getAttributes().originalState)
}
}
return {
submit,
updateRepository,
entry,
quitForm,
}
</script>
<template>
<v-form
ref="form"
lazy-validation
:readonly="readonly"
>
(...)
<UiButtonSubmit
v-if="!readonly"
@submit="submit"
:actions="actions"
></UiButtonSubmit>
</v-form>
</template>
Ce qui pourrait peut-être donner quelque chose comme :
pages/organization/contact_points/_id.vue :
<script>
setup () {
const id = parseInt(route.value.params.id)
return {
id
}
</script>
<template>
<FormOrganizationContactPoint :id="id"></FormOrganizationContactPoint>
</template>
components/Form/Organization/ContactPoint.vue :
<script>
setup () {
const { em } = useEntityManager()
const { fetchState, contactPoint } = props.id ? em.fetch(ContactPoint, props.id) : em.new(ContactPoint)
(...)
return {
contactPoint,
fetchState,
(...)
}
}
</script>
<template>
<UiForm
v-if="!fetchState.pending"
:id="id"
:model="model"
:submitActions="submitActions">
>
<template #form.input="{contactPoint}">
(...)
</template>
</UiForm>
</template>
Dans components/Ui/Form.vue :
<script>
setup () {
const { em } = useEntityManager()
const { id }: ToRefs = toRefs(props)
(...)
const submit = async (next: string|null = null) => {
if(await validate()){
markAsNotDirty()
em.persist(contactPoint)
(...)
}
const quitForm = () => {
// reset entity
em.reset(contactPoint)
}
return {
submit,
contactPoint,
quitForm,
}
</script>
<template>
<v-form
ref="form"
lazy-validation
:readonly="readonly"
>
(...)
<UiButtonSubmit
v-if="!readonly"
@submit="submit"
:actions="actions"
></UiButtonSubmit>
</v-form>
</template>
Exemple sur la page components/Form/Organization/ContactPoint.vue :
<UiInputEnum
field="contactType"
label="contactpoint_type"
:data="entry['contactType']"
enum-type="contact_point_type"
@update="updateRepository"
/>
Dans components/Ui/Input/Enum.vue :
const { enumType } = props
const { $dataProvider, store } = useContext()
(...)
const items: Ref<Array<EnumChoices>> = ref([])
useFetch(async () => {
items.value = await $dataProvider.invoke({
type: QUERY_TYPE.ENUM,
enumType
})
})
return {
items,
(...)
}
Ce qui pourrait peut-être donner quelque chose comme :
components/Ui/Input/Enum.vue :
const { enumType } = props
const { enumManager } = useEnumManager()
(...)
const items: Ref<Array<EnumChoices>> = enumManager.fetchByType(enumType)
return {
items,
(...)
}
Exemple sur la page components/Form/Organization/Address.vue :
<script>
const {countries, fetchState: countriesFetchingState} = useCountryProvider($dataProvider)
return {
(...)
countries,
countriesFetchingState,
(...)
}
</script>
<template>
<UiInputAutocomplete
field="addressPostal.addressCountry"
label="addressCountry"
:data="getIdFromUri(entry['addressPostal.addressCountry'])"
:items="countries"
:isLoading="countriesFetchingState.pending"
:item-text="['name']"
@update="updateRepository(`/api/countries/${$event}`, 'addressPostal.addressCountry')"
/>
</template>
Dans composables/data/useCountryProvider.ts :
export function useCountryProvider($dataprovider: DataProvider){
const {fetch, fetchState} = useFetch(async () => {
await $dataprovider.invoke({
type: QUERY_TYPE.MODEL,
model: Country
})
})
const countries = computed(() => {
return repositoryHelper.findCollectionFromModel(Country)
})
return {
countries,
fetch,
fetchState
}
}
Dans services/store/repository.ts :
public findCollectionFromModel (model: typeof Model, orderBy?: OrderByVuexOrm): Collection {
const repository = this.getRepository(model)
if(orderBy){
for(const orderKey in orderBy){
repository.orderBy(orderKey, orderBy[orderKey])
}
}
return repository.all()
}
Ce qui pourrait peut-être donner quelque chose comme :
page components/Form/Organization/Address.vue :
<script>
const { em } = useEntityManager()
const { countriesFetchingState, countries } = em.fetchAll(Country)
return {
(...)
countries,
countriesFetchingState,
(...)
}
</script>
Exemple sur la page components/Form/Organization/Address.vue :
<script>
const {searchFunction: addressSearch, updateCpAddress} = useAddressPostalUtils($dataProvider)
return {
(...)
addressSearch,
updateCpAddress,
(...)
}
</script
<template>
(...)
<UiInputAutocompleteWithAPI
field="owner"
label="importAddress"
:item-text="['person.givenName', 'person.name']"
:searchFunction="accessSearch"
@update="updateAccessAddress"
/>
(...)
<UiInputAutocompleteWithAPI
field="addressPostal.postalCode"
label="postalCode"
:data="getAutoCompleteAddressItem"
:item-text="['postcode']"
:slotText="['postcode', 'city']"
:searchFunction="addressSearch"
:returnObject="true"
@update="updateCpAddress($event, updateRepository)"
/>
<UiInputAutocompleteWithAPI
field="addressPostal.addressCity"
label="addressCity"
:data="getAutoCompleteAddressItem"
:item-text="['city']"
:slotText="['postcode', 'city']"
:searchFunction="addressSearch"
:returnObject="true"
@update="updateCpAddress($event, updateRepository)"
/>
(...)
</template>
Dans components/Ui/Input/AutocompleteWithAPI.vue :
useFetch(async () => {
isLoading.value = true
const r = await $dataProvider.invoke({
type: QUERY_TYPE.DEFAULT,
url: props.remoteUrl,
listArgs: {
filters:[
{key: 'id', value: ids.join(',')}
]
}
})
isLoading.value = false
remoteData.value = r.data
items.value = r.data
})
Dans composables/data/useAddressPostalUtils.ts :
async function searchFunction(research: string, field: string): Promise<Array<AnyJson>> {
if (research) {
const response = await $dataProvider.invoke({
type: QUERY_TYPE.DEFAULT,
url: `https://api-adresse.data.gouv.fr/search/?q=${research}&type=municipality&autocomplete=1&limit=20`,
params: {
noXaccessId: true
}
})
const apiResponse = response.data.features.map((data: AnyJson) => data.properties)
(...)
}
Alternative :
composables/data/useAddressPostalUtils.ts
async function searchFunction(research: string, field: string): Promise<Array<AnyJson>> {
if (research) {
const response = await Connection.get({
url: `https://api-adresse.data.gouv.fr/search/?q=${research}&type=municipality&autocomplete=1&limit=20`,
params: {
noXaccessId: true
}
})
const apiResponse = response.features.map((data: AnyJson) => data.properties)
(...)
}
Sur la page pages/organization/index.vue :
<UiImage
:id="getIdFromUri(entry['logo'])"
:upload="true"
:width="200"
field="logo"
:ownerId="id"
@update="updateRepository"
></UiImage>
Dans components/Ui/Image.vue :
<script>
const {getOne, provideImg} = useImageProvider($dataProvider, $config)
const { imageLoaded, fetch } = getOne(props.id, props.imageByDefault, props.height, props.width)
</script>
<template>
<v-img
:src="imgSrcReload ? imgSrcReload : imageLoaded"
:lazy-src="require(`/assets/images/byDefault/${imageByDefault}`)"
:height="height"
:width="width"
aspect-ratio="1"
>(...)</v-img>
<UiInputImage
v-if="openUpload"
@close="openUpload=false"
:existingImageId="id"
:field="field"
:ownerId="ownerId"
@update="$emit('update', $event, field); openUpload=false"
@reload="fetch();openUpload=false"
@reset="reset"
></UiInputImage>
</template>
Dans components/Ui/Input/Image.vue :
const {$dataProvider, $config, $dataPersister} = useContext()
const {getOne} = useImageProvider($dataProvider, $config)
const fileToSave = new File()
const cropper:Ref<any> = ref(null)
const image: Ref<AnyJson> = ref({
id: null,
src: null,
file: null,
name: null
})
(...)
if(props.existingImageId){
useFetch(async () => {
const result: ApiResponse = await $dataProvider.invoke({
type: QUERY_TYPE.DEFAULT,
url: 'api/files',
id: props.existingImageId
})
const config = JSON.parse(result.data.config)
(...)
})
}
const { fetchState, imageLoaded } = getOne(props.existingImageId)
const unwatch: WatchStopHandle = watch(imageLoaded, (newValue, oldValue) => {
if (newValue === oldValue || typeof newValue === 'undefined') { return }
image.value.src = newValue
})
const save = async () => {
fileToSave.config = JSON.stringify({
x: coordinates.value.left,
y: coordinates.value.top,
height: coordinates.value.height,
width: coordinates.value.width
})
//Cas d'un PUT : l'image existe déjà on bouge simplement le cropper
if(image.value.id){
fileToSave.id = image.value.id
repositoryHelper.persist(File, fileToSave)
await $dataPersister.invoke({
type: QUERY_TYPE.MODEL,
model: File,
id: fileToSave.id
})
//On émet un évent afin de mettre à jour le formulaire de départ
emit('reload')
}
//Post : on créer une nouvelle image donc on passe par l'api legacy...
else{
if(image.value.file){
//On créer l'objet File à sauvegarder
fileToSave.name = image.value.name
fileToSave.imgFieldName = props.field
fileToSave.visibility = 'EVERYBODY'
fileToSave.folder = 'IMAGES'
if(props.ownerId)
fileToSave.ownerId = props.ownerId
//Appel au datapersister
const response: ApiResponse = await $dataPersister.invoke({
type: QUERY_TYPE.FILE,
baseUrl: $config.baseURL_Legacy,
data: fileToSave.$toJson(),
file: image.value.file
})
//On émet un évent afin de mettre à jour le formulaire de départ
emit('update', response.data['@id'])
}else{
//On reset l'image : on a appuyer sur "poubelle" puis on enregistre
emit('reset')
}
}
}
Dans composables/data/useImageProvider.ts :
async function provideImg(id: number, height: number = 0, width: number = 0) {
return await $dataProvider.invoke({
type: QUERY_TYPE.IMAGE,
baseUrl: $config.baseURL_Legacy,
imgArgs: {
id: id,
height: height,
width: width
}
})
}
Alternative :
pages/organization/index.vue :
<UiImage
:id="getIdFromUri(organization['logo'])"
:upload="true"
:width="200"
field="logo"
:ownerId="id"
></UiImage>
components/Ui/Image.vue :
<script>
const { imageManager } = useImageManager()
const { imageLoaded, image } = imageManager.fetch(props.id, props.height, props.width)
</script>
components/Ui/Input/Image.vue :
const { em } = useEntityManager()
const { imageManager } = useImageManager()
const cropper:Ref<any> = ref(null)
const image: Ref<AnyJson> = ref({
id: null,
src: null,
file: null,
name: null
})
(...)
if(props.existingImageId){
useFetch(async () => {
const result: ApiResponse = await $dataProvider.invoke({
type: QUERY_TYPE.DEFAULT,
url: 'api/files',
id: props.existingImageId
})
const { fetchState, imageLoaded } = imageManager.fetch(props.existingImageId)
const config = JSON.parse(imageLoaded.data.config)
(...)
})
}
// je suis pas sûr de comprendre la différence entre l'appel précédent (dans le if) et celui ci?
const { fetchState, imageLoaded } = getOne(props.existingImageId)
(...)
const save = async () => {
fileToSave.config = JSON.stringify({
x: coordinates.value.left,
y: coordinates.value.top,
height: coordinates.value.height,
width: coordinates.value.width
})
//Cas d'un PUT : l'image existe déjà on bouge simplement le cropper
if (image.value.id) {
em.persist(image.value)
//On émet un évent afin de mettre à jour le formulaire de départ
emit('reload')
}
//Post : on créer une nouvelle image donc on passe par l'api legacy...
else{
if (image.value.file) {
//On créer l'objet File à sauvegarder
const file = em.new(File)
file.name = image.value.name
file.imgFieldName = props.field
file.visibility = 'EVERYBODY'
file.folder = 'IMAGES'
if (props.ownerId)
file.ownerId = props.ownerId
//Appel au datapersister
em.persist(file)
//On émet un évent afin de mettre à jour le formulaire de départ
emit('update', response.data['@id'])
} else {
//On reset l'image : on a appuyer sur "poubelle" puis on enregistre
emit('reset')
}
}
}
(exemple?)
Dans components/Layout/Header/Notification.vue :
const {fetch, fetchState} = useFetch(async () => {
data.value = await $dataProvider.invoke({
type: QUERY_TYPE.MODEL,
model: Notification,
listArgs: {
itemsPerPage: 10,
page: page.value
}
})
loading.value = false
})
const notifications: ComputedRef = computed(() => {
const query = repositoryHelper.getRepository(Notification).with('message').orderBy('id', 'desc')
return queryHelper.getCollection(query)
})
(...)
const markNotificationsAsRead = () => {
unreadNotification.value.map((notification:Notification)=>{
notification.notificationUsers = ['read']
repositoryHelper.persist(Notification, notification)
createNewNotificationUsers(notification)
})
}
const createNewNotificationUsers = (notification: Notification) =>{
const newNotificationUsers = repositoryHelper.persist(NotificationUsers, new NotificationUsers(
{
access:`/api/accesses/${currentAccessId}`,
notification:`/api/notifications/${notification.id}`,
isRead: true
}
)) as NotificationUsers
$dataPersister.invoke({
type: QUERY_TYPE.MODEL,
model: NotificationUsers,
idTemp: newNotificationUsers.id,
showProgress: false
})
}
Alternative :
components/Layout/Header/Notification.vue :
const { em } = useEntityManager()
const notifications: Ref = em.fetchAll(Notification)
(...)
const markNotificationsAsRead = () => {
unreadNotification.value.map((notification: Notification) => {
notification.notificationUsers = ['read']
const notificationUser = em.new(NotificationUsers)
notificationUser.access = `/api/accesses/${currentAccessId}`
notificationUser.notification = `/api/notifications/${notification.id}`
notificationUser.isRead = true
em.persist(notificationUser)
})
}
Sur la page pages/organization/index.vue :
const checkSiretHook = async (siret: string, field: string, updateRepository: any) => {
await checkSiret(siret)
if (!siretError.value) { updateRepository(siret, field) }
}
Dans composables/form/useValidator.ts :
function useHandleSiret() {
const siretError: Ref<boolean> = ref(false)
const siretErrorMessage: Ref<string> = ref('')
const checkSiret = async (siret: string) => {
const response = await $dataProvider.invoke({
type: QUERY_TYPE.DEFAULT,
url: '/api/siret-checking',
id: siret
})
if (typeof response !== 'undefined') {
siretError.value = !response.isCorrect
siretErrorMessage.value = response.isCorrect ? '' : i18n.t('siret_error') as string
} else {
siretError.value = false
siretErrorMessage.value = ''
}
}
return {
siretError,
siretErrorMessage,
checkSiret
}
}
Alternative :
composables/form/useValidator.ts
function useHandleSiret() {
const siretError: Ref<boolean> = ref(false)
const siretErrorMessage: Ref<string> = ref('')
const checkSiret = async (siret: string) => {
const response = await Connection.get({
url: '/api/siret-checking/' + siret,
})
if (typeof response !== 'undefined') {
siretError.value = !response.isCorrect
siretErrorMessage.value = response.isCorrect ? '' : i18n.t('siret_error') as string
} else {
siretError.value = false
siretErrorMessage.value = ''
}
}
return {
siretError,
siretErrorMessage,
checkSiret
}
}
Dans components/Ui/Template/MobytStatus.vue :
const mobytStatus: Ref<MobytUserStatus | null> = ref(null)
// fetch the mobyt status
const { fetchState } = useFetch(async () => {
try {
const response:ApiResponse = await $dataProvider.invoke({
type: QUERY_TYPE.DEFAULT,
url: '/api/mobyt/status/' + id
})
mobytStatus.value = response.data as MobytUserStatus
if(!mobytStatus.value?.active) emit('disabled_sms_row')
} catch (Error) {
// eslint-disable-next-line no-console
console.error('Error: Mobyt status not found')
}
})
Même remarque que pour le 1-, ce serait mieux d'utiliser directement le service Connection :
async getUserProfile ({ dispatch }) {
const myProfile = await this.app.context.$connection.get('/api/my_profile')
await dispatch('profile/access/setProfile', myProfile)
}
// fetch the mobyt status
const { fetchState } = useFetch(async () => {
try {
const response: ApiResponse = await $connection.get('/api/mobyt/status/' + id)
mobytStatus.value = response as MobytUserStatus
if(!mobytStatus.value?.active) emit('disabled_sms_row')
} catch (Error) {
// eslint-disable-next-line no-console
console.error('Error: Mobyt status not found')
}
})