add component and type to add user dynamically

This commit is contained in:
Julien Fastré 2022-01-03 15:42:24 +01:00
parent 5bf1b9d8bd
commit 45dd21e02a
12 changed files with 334 additions and 6 deletions

View File

@ -14,6 +14,7 @@ namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -24,11 +25,8 @@ class NotificationType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder $builder
->add('addressees', EntityType::class, [ ->add('addressees', PickUserDynamicType::class, [
'class' => User::class,
'choice_label' => 'label',
'multiple' => true, 'multiple' => true,
'by_reference' => true,
]) ])
->add('message', ChillTextareaType::class, [ ->add('message', ChillTextareaType::class, [
'required' => false, 'required' => false,

View File

@ -0,0 +1,70 @@
<?php
namespace Chill\MainBundle\Form\Type\DataTransformer;
use Chill\MainBundle\Entity\User;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface;
class UserToJsonTransformer implements DataTransformerInterface
{
private SerializerInterface $serializer;
private DenormalizerInterface $denormalizer;
private bool $multiple;
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, bool $multiple)
{
$this->denormalizer = $denormalizer;
$this->serializer = $serializer;
$this->multiple = $multiple;
}
/**
* @param User|User[] $value
*/
public function transform($value): string
{
if (null === $value) {
return $this->multiple ? "null" : "[]";
}
return $this->serializer->serialize($value, 'json', [
AbstractNormalizer::GROUPS => ['read'],
]);
}
public function reverseTransform($value)
{
if ($this->multiple) {
return \array_map(
function($item) { return $this->denormalizeOne($item);},
json_decode($value, true)
);
}
return $this->denormalizeOne(json_decode($value, true));
}
private function denormalizeOne(array $item): User
{
if (!array_key_exists('type', $item)) {
throw new TransformationFailedException('the key "type" is missing on element');
}
if (!array_key_exists('id', $item)) {
throw new TransformationFailedException('the key "id" is missing on element');
}
return
$this->denormalizer->denormalize(
['type' => $item['type'], 'id' => $item['id']],
User::class,
'json',
[AbstractNormalizer::GROUPS => ['read']],
);
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Chill\MainBundle\Form\Type;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\DataTransformer\UserToJsonTransformer;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Pick user dymically, using vuejs module "AddPerson"
*/
class PickUserDynamicType extends AbstractType
{
private SerializerInterface $serializer;
private DenormalizerInterface $denormalizer;
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer)
{
$this->denormalizer = $denormalizer;
$this->serializer = $serializer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addViewTransformer(new UserToJsonTransformer($this->denormalizer, $this->serializer, $options['multiple']));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefault('multiple', false)
->setAllowedTypes('multiple', ['bool'])
->setDefault('compound', false)
;
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['multiple'] = $options['multiple'];
$view->vars['types'] = ['user'];
$view->vars['uniqid'] = uniqid('pick_user_dyn');
}
public function getBlockPrefix()
{
return 'pick_user_dynamic';
}
}

View File

@ -0,0 +1,59 @@
import {createApp} from 'vue';
import PickEntity from 'ChillMainAssets/vuejs/PickEntity/PickEntity.vue';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
import {activityMessages} from "../../../../../ChillActivityBundle/Resources/public/vuejs/Activity/i18n";
window.addEventListener('DOMContentLoaded', function(e) {
let
apps = document.querySelectorAll('[data-module="pick-dynamic"]');
apps.forEach(function(el) {
const
isMultiple = parseInt(el.dataset.multiple) === 1,
input = document.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'),
picked = isMultiple ? JSON.parse(input.value) : [JSON.parse(input.value)],
i18n = _createI18n({});
createApp({
template: '<pick-entity :multiple="multiple" :types="types" :picked="picked" :uniqid="uniqid" @addNewEntity="addNewEntity" @removeEntity="removeEntity"></pick-entity>',
components: {
PickEntity,
},
data() {
return {
multiple: isMultiple,
types: JSON.parse(el.dataset.types),
picked,
uniqid: el.dataset.uniqid,
}
},
methods: {
addNewEntity(entity) {
console.log('addNewEntity', entity);
if (this.multiple) {
console.log('adding multiple');
if (!this.picked.some(el => {
return el.type === entity.type && el.id === entity.id;
})) {
this.picked.push(entity);
input.value = JSON.stringify(this.picked);
}
} else {
if (!this.picked.some(el => {
return el.type === entity.type && el.id === entity.id;
})) {
this.picked.splice(0, this.picked.length);
this.picked.push(entity);
input.value = JSON.stringify(this.picked[0]);
}
}
},
removeEntity(entity) {
console.log('removeEntity', entity);
this.picked = this.picked.filter(e => !(e.type === entity.type && e.id === entity.id));
input.value = JSON.stringify(this.picked);
},
}
}).use(i18n).mount(el);
});
});

View File

@ -0,0 +1,97 @@
<template>
<ul class="list-suggest remove-items">
<li v-for="p in picked" @click="removeEntity(p)" :key="p.type+p.id">
<span class="chill_denomination">{{ p.text }}</span>
</li>
</ul>
<ul class="record_actions">
<li>
<AddPersons
:options="addPersonsOptions"
:key="uniqid"
buttonTitle="pick_person.add"
modalTitle="translatedListOfTypes"
ref="addPersons"
@addNewPersons="addNewEntity"
>
</AddPersons>
</li>
</ul>
</template>
<script>
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import messages from "./i18n";
console.log('messages', messages);
export default {
name: "PickEntity",
messages,
props: {
multiple: {
type: Boolean,
required: true,
},
types: {
type: Array,
required: true,
},
picked: {
required: true,
},
uniqid: {
type: String,
required: true,
}
},
emits: ['addNewEntity', 'removeEntity'],
components: {
AddPersons,
},
data() {
return {
key: ''
};
},
computed: {
addPersonsOptions() {
return {
uniq: !this.multiple,
type: this.types,
priority: null,
button: {
size: 'btn-sm',
class: 'btn-submit',
},
};
},
translatedListOfTypes() {
let trans = [];
this.types.forEach(t => {
trans.push(this.$t('pick_entity.'.t));
})
return trans.join(', ');
}
},
methods: {
addNewEntity({ selected, modal }) {
selected.forEach((item) => {
this.$emit('addNewEntity', item.result);
}, this
);
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
},
removeEntity(entity) {
console.log('remove entity', entity);
this.$emit('removeEntity', entity);
}
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,17 @@
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
const messages = {
fr: {
pick_entity: {
add: 'Ajouter',
modal_title: 'Ajouter des',
user: 'Utilisateurs',
person: 'Usagers',
thirdparty: 'Tiers',
}
}
}
const full = Object.assign(messages, personMessages);
export default full;

View File

@ -215,3 +215,8 @@
{{ form_widget(form.center) }} {{ form_widget(form.center) }}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block pick_user_dynamic_widget %}
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
<div data-module="pick-dynamic" data-types="{{ form.vars['types']|json_encode }}" data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}"></div>
{% endblock %}

View File

@ -1,5 +1,15 @@
{% extends "@ChillMain/layout.html.twig" %} {% extends "@ChillMain/layout.html.twig" %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-md-10"> <div class="col-md-10">

View File

@ -1,5 +1,15 @@
{% extends "@ChillMain/layout.html.twig" %} {% extends "@ChillMain/layout.html.twig" %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-md-10"> <div class="col-md-10">

View File

@ -63,6 +63,7 @@ module.exports = function(encore, entries)
encore.addEntry('mod_blur', __dirname + '/Resources/public/module/blur/index.js'); encore.addEntry('mod_blur', __dirname + '/Resources/public/module/blur/index.js');
encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js'); encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js');
encore.addEntry('mod_notification_toggle_read_status', __dirname + '/Resources/public/module/notification/toggle_read.js'); encore.addEntry('mod_notification_toggle_read_status', __dirname + '/Resources/public/module/notification/toggle_read.js');
encore.addEntry('mod_pickentity_type', __dirname + '/Resources/public/module/pick-entity/index.js');
// Vue entrypoints // Vue entrypoints
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js'); encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');

View File

@ -18,8 +18,8 @@ services:
Chill\MainBundle\Form\Type\: Chill\MainBundle\Form\Type\:
resource: '../Form/Type' resource: '../Form/Type'
tags: autoconfigure: true
- { name: form.type } autowire: true
Chill\MainBundle\Doctrine\Event\: Chill\MainBundle\Doctrine\Event\:
resource: '../Doctrine/Event/' resource: '../Doctrine/Event/'

View File

@ -93,9 +93,11 @@ import PersonSuggestion from './AddPersons/PersonSuggestion';
import { searchEntities } from 'ChillPersonAssets/vuejs/_api/AddPersons'; import { searchEntities } from 'ChillPersonAssets/vuejs/_api/AddPersons';
import { postPerson } from "ChillPersonAssets/vuejs/_api/OnTheFly"; import { postPerson } from "ChillPersonAssets/vuejs/_api/OnTheFly";
import { postThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly"; import { postThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly";
import {messages} from 'ChillPersonAssets/vuejs/_js/i18n.js';
export default { export default {
name: 'AddPersons', name: 'AddPersons',
messages,
components: { components: {
Modal, Modal,
PersonSuggestion, PersonSuggestion,