Cannot read properties of undefined (reading 'toUpperCase') with select2 and Pugx - symfony

For an autocomplete with symfony, I use the bundle PUGX but using select2 version 3.5.2-browserify throw me an error :
Cannot read properties of undefined (reading 'toUpperCase')
My JS file :
import $ from 'jquery';
import 'select2/select2';
import '#pugx/autocompleter-bundle/js/autocompleter-select2';
$('#postal_code_search_zipcode').autocompleter({
url_list: '/postalCode_search',
url_get: '/postalCode_get/',
// min_length: 3,
otherOptions: {
minimumInputLength: 3,
formatNoMatches: 'Pas trouvé',
formatSearching: 'Je cherche',
formatInputTooShort: 'Insérer au minimum les 3 premiers chiffre'
}
})
My Controller :
{
#[Route('/postalCode_search', name: "postalCode_search", defaults:["_format" => "json"])]
public function searchPostalCode(Request $request, CityRepository $cityRepository): Response
{
$query = $request->query->get('q');
$results = $cityRepository->findLikePostalCode($query);
return $this->render('json/postal_code.json.twig', ['postalCodes' => $results]);
}
#[Route('/postalCode_get', name: "postalCode_get")]
public function getPostalCode(string $id, CityRepository $cityRepository): Response
{
$postalCode = $cityRepository->find($id);
if (null !== $postalCode){
$result = $postalCode->getZipcode().' '.$postalCode->getName();
}else{
$result = 'Aucun résultat';
}
return $this->json($result);
}
}
My json twig:
[{% for postalCode in postalCodes -%}
{{ {id: postalCode.id, label: postalCode.zipcode ~ ' ' ~ postalCode.name, value: postalCode.zipcode ~ ' ' ~ postalCode.name}|json_encode|raw }}
{%- if not loop.last %},
{% endif -%}
{%- endfor %}
]
If I use jquery-ui instead, when typing, it show me all I need but not with select2, any idea?

Related

xhr.send is not sending data in symfony how do i fix it?

I'm having a problem with xhr.send(JSON.stringify(donnees)) , im trying to edit with calendar but it's not sending data.
here is my code.
this is the twig part :`
{% extends 'base-back.html.twig' %}
{% block body %}
<br>
{% block title %}<center><h1>Vols Calendar</h1></center>{% endblock %}
<br><br><br>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar#5.6.0/main.min.js" integrity="sha256-ekrJn2FeZaiUFq99QswpQCUTME/HxaDfX7R8INzKJGE=" crossorigin="anonymous"></script>
<div id="calendrier">
<script src="{{ asset('assets/js/fullcalendar.js') }}"></script>
</div>
{% block stylesheet %}
<style>
#calendrier{
width: 600px;
margin: auto;
height:60%;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar#5.6.0/main.min.css" integrity="sha256-uq9PNlMzB+1h01Ij9cx7zeE2OR2pLAfRw3uUUOOPKdA=" crossorigin="anonymous">
{% endblock %}
{% block javascript %}
<script>
window.onload = () =>{
let calendarElt=document.querySelector("#calendrier");
let calendar =new FullCalendar.Calendar(calendarElt,{
initialView: 'dayGridMonth',
locale: 'fr',
timeZone: 'Afrique/Tunisie',
headerToolbar: {
start: 'prev,next today',
center: 'title',
end: 'dayGridMonth'
},
events: {{data|raw}},
//can edit
editable: true,
//can make it 2 days for example
eventResizableFromStart: true
});
//get an object when you move an event
calendar.on('eventChange', (e)=>{
let url =`/vols/api/${e.event.id}/edit`
let donnees={
"destination_aller":e.event.extendedProps.destination_aller,
"destination_retour":e.event.extendedProps.destination_retour,
"title":e.event.title,
"start":e.event.start,
"date_retour":e.event.extendedProps.date_retour,
"passagers":e.event.extendedProps.passagers,
"cabine":e.event.extendedProps.cabine,
}
console.log(donnees);
let xhr = new XMLHttpRequest()
xhr.open("PUT", url)
xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");
xhr.send(JSON.stringify(donnees))
})
calendar.render();
}
</script>
{% endblock %}{% endblock %}
and this is my controller :
/**
* #Route("/calendar", name="calendar")
*/
public function calendar()
{
$repository = $this->getDoctrine()->getRepository(vols::class);
$event = $repository->findAll();
$rdvs=[];
foreach($event as $event){
$rdvs[]=[
'id' => $event->getId(),
'destination_aller' => $event->getDestinationAller(),
'destination_retour'=>$event->getDestinationRetour(),
'title' => $event->getVoyage(),
'start' => $event->getDateDepart()->format('Y-m-d'),
'date_retour' => $event->getDateRetour(),
'passagers' => $event-> getPassagers(),
'cabine'=>$event->getCabine(),
];
}
$data= json_encode($rdvs);
return $this->render('vols/Callender.html.twig', compact('data'));
}
/**
* #Route("/api/{id}/edit", name="editCalendar", methods={"PUT"})
*/
public function editCalendar(vols $calendar, Request $request) {
//récuperation de données
$donnees=json_decode($request->getContent());
if(
isset($donnees->destination_aller) && !empty($donnees->destination_aller) &&
isset($donnees->destination_retour) && !empty($donnees->destination_retour) &&
isset($donnees->title) && !empty($donnees->title) &&
isset($donnees->start) && !empty($donnees->start) &&
isset($donnees->date_retour) && !empty($donnees->date_retour) &&
isset($donnees->passagers) && !empty($donnees->passagers) &&
isset($donnees->cabine) && !empty($donnees->cabine)
){
//données completes, on initialise un code
$code = 200;
//verification de l'id existe
if(!$calendar){
//on instancie un rende
$calendar = new vols;
//change code
$code=201;
}
//on hydrate l'objet avec les données
$calendar->setDestinationAller($donnees->destination_aller);
$calendar->setDestinationRetour($donnees->destination_retour);
$calendar->setVoyage($donnees->title);
$calendar->setDateDepart(new DateTime($donnees->start));
$calendar->setDateRetour(new DateTime($donnees->date_retour));
$calendar->setPassagers($donnees->passagers);
$calendar->setCabine($donnees->cabine);
$em=$this->getDoctrine()->getManager();
$em->persist($calendar);
$em->flush();
//return code
return new Response('Ok', $code);
}else{
//données non completes
return new Response('data not complete', 404);
}
return $this->render('vols/calenderEdit.html.twig');
}
my data is sent but i can't edit it
thank you.

Using ElFinder on Symfony: I can't select images

I want to use ElFinder as my file manager in my Symfony project. I followed the doc on GitHub. In my routes.yaml:
elfinder:
resource: '#FMElfinderBundle/Resources/config/routing.yaml'
security.yaml
- { path: ^/efconnect, role: [ROLE_USER] }
- { path: ^/elfinder, role: [ROLE_USER] }
And finally on fm_elfinder.yaml
fm_elfinder:
instances:
default:
locale: '%locale%' # defaults to current request locale
editor: ckeditor # other options are tinymce, tinymce4, fm_tinymce, form, simple, custom
connector:
roots:
uploads:
driver: LocalFileSystem
path: uploads
upload_max_size: 2M
Then I added it on a Admin element on Sonata, like this:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Contenu')
->add('published', CheckboxType::class, ['required' => false, 'label' => 'Publier'])
->add('title', TextType::class, ['required' => true, 'label' => 'Titre'])
->add('textLink', TextType::class, ['required' => true, 'label' => 'Texte du lien'])
->add('media', ElFinderType::class, array(
'label' => 'Photo',
'enable' => true,
'required' => true,
'instance' => 'default',
'attr' => array('class' => 'form-control')
)
)
->end();
}
Then I go on Sonata Admin, and when I try to add an image, a window open, I added an jpeg but then when i click on it, nothing seems to happen. Like, I can select it but my window stay open and my field doesn't fill up with the name of the image.
Thanks for your help.
Ok I got it to work on my easyadmin forms. This is the easyAdmin 2 configuration :
easy_admin:
entities:
BlogPost:
class: App\Entity\BlogPost
form:
fields:
- { property: 'title', label: 'page.title' }
- { property: 'image', type: 'App\Form\ElFinderType', label: 'blog.image.file', type_options: { enable: true, instance: 'single' } }
(I copied the vendor/helios-ag/fm-elfinder-bundle/src/Form/Type/ElFinderType.php file to App\Form\ElFinderType.php. This is not required).
I set the fm_elfinder connector to simple :
# src/packages/fm_elfinder.yaml
fm_elfinder:
instances:
single:
locale: fr
editor: simple
theme: smoothness
relative_path: false
connector:
roots:
uploads:
driver: LocalFileSystem
path: uploads/images
show_hidden: false
upload_allow: [ 'image/png', 'image/jpg', 'image/jpeg' ]
upload_deny: [ 'all' ]
upload_max_size: 2M
Next I copied two of the template files from the bundle to my templates\bundles directory :
{# templates/bundles/FMElfinderBundle/Form/elfinder_widget.html.twig #}
{% block elfinder_widget %}
<div class="buttons">
<input type="button" {{ block('widget_attributes') }}
data-type="elfinder-remove-button" value="Delete" class="{% if value is empty %}hide{% endif %} remove-button"/>
<input type="button" {{ block('widget_attributes') }}
data-type="elfinder-add-button" value="Add" class="{% if value is not empty %}hide{% endif %} add-button"/>
</div>
{% if value is not empty %}
<img src="{{ value }}" alt="">
{% endif %}
<input type="text" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}
data-type="elfinder-input-field" class="hide"/>
{% if enable and instance is defined %}
<script type="text/javascript" charset="utf-8">
const addButtonSelector = '[data-type="elfinder-add-button"][id="{{ id }}"]';
const removeButtonSelector = '[data-type="elfinder-remove-button"][id="{{ id }}"]';
const inputSelector = '[data-type="elfinder-input-field"][id="{{ id }}"]';
live('click', addButtonSelector, () => {
window.open("{{ path('elfinder', {'instance': instance, 'homeFolder': homeFolder }) }}?id={{ id }}", "popupWindow", "height=450, width=900");
});
removeButtonListener()
function live(eventType, elementQuerySelector, cb) {
document.addEventListener(eventType, function (event) {
const qs = document.querySelectorAll(elementQuerySelector);
if (qs) {
let el = event.target, index = -1;
while (el && ((index = Array.prototype.indexOf.call(qs, el)) === -1)) {
el = el.parentElement;
}
if (index > -1) {
cb.call(el, event);
}
}
})
}
function setValue(value) {
document.querySelector(inputSelector).value = value;
}
function displayImage(value) {
const inputElement = document.querySelector(inputSelector);
const parent = inputElement.parentElement
const image = document.createElement("img");
image.src = value;
parent.append(image)
const removeButton = document.querySelector(removeButtonSelector);
const addButton = document.querySelector(addButtonSelector);
removeButton.classList.remove('hide')
addButton.classList.add('hide')
removeButtonListener()
}
function removeButtonListener() {
const removeButtonElement = document.querySelector(removeButtonSelector);
if(removeButtonElement){
removeButtonElement.addEventListener('click', () => {
const addButtonElement = document.querySelector(addButtonSelector);
removeButtonElement.classList.remove('hide')
const parent = removeButtonElement.closest('.form-widget')
const inputElement = parent.querySelector('[data-type="elfinder-input-field"]');
inputElement.value = null
const imageElement = parent.getElementsByTagName('img')[0]
if (imageElement) {
parent.removeChild(imageElement)
}
removeButtonElement.classList.add('hide')
addButtonElement.classList.remove('hide')
document.querySelector(inputSelector).value = null;
})
}
}
</script>
{% endif %}
{% endblock %}
This first one contains the fields and the JavaScript needed to fill them in. I added a Add button. This will open the elfinder, once an image gets selected this button becomes hidden and a Remove button will be added.
Next is the ElFinder window :
{# templates/bundles/FMElfinderBundle/Elfinder/simple.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<script data-main="{{ path('ef_main_js') }}"
src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script>
function getUrlParam(paramName) {
const reParam = new RegExp('(?:[\?&]|&)' + paramName + '=([^&]+)', 'i');
const match = window.location.search.match(reParam);
return (match && match.length > 1) ? match[1] : '' ;
}
define('elFinderConfig', {
// elFinder options (REQUIRED)
// Documentation for client options:
// https://github.com/Studio-42/elFinder/wiki/Client-configuration-options
defaultOpts: {
url: '{{ path('ef_connect', { 'instance': instance, 'homeFolder': homeFolder } ) }}',
lang: '{{ locale }}',
onlyMimes: {{ onlyMimes|raw }},
getFileCallback: function (file) {
window.opener.setValue(file.url);
window.opener.displayImage(file.url);
window.close();
},
commandsOptions: {
edit: {
extraOptions: {
// set API key to enable Creative Cloud image editor
// see https://console.adobe.io/
creativeCloudApiKey: '',
// browsing manager URL for CKEditor, TinyMCE
// uses self location with the empty value
managerUrl: ''
}
},
quicklook: {
// to enable CAD-Files and 3D-Models preview with sharecad.org
sharecadMimes: ['image/vnd.dwg', 'image/vnd.dxf', 'model/vnd.dwf', 'application/vnd.hp-hpgl', 'application/plt', 'application/step', 'model/iges', 'application/vnd.ms-pki.stl', 'application/sat', 'image/cgm', 'application/x-msmetafile'],
// to enable preview with Google Docs Viewer
googleDocsMimes: ['application/pdf', 'image/tiff', 'application/vnd.ms-office', 'application/msword', 'application/vnd.ms-word', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/postscript', 'application/rtf'],
// to enable preview with Microsoft Office Online Viewer
// these MIME types override "googleDocsMimes"
officeOnlineMimes: ['application/vnd.ms-office', 'application/msword', 'application/vnd.ms-word', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation']
}
},
// bootCallback calls at before elFinder boot up
bootCallback: function (fm, extraObj) {
/* any bind functions etc. */
fm.bind('init', function () {
});
// for example set document.title dynamically.
var title = document.title;
fm.bind('open', function () {
var path = '',
cwd = fm.cwd();
if (cwd) {
path = fm.path(cwd.hash) || null;
}
document.title = path ? path + ':' + title : title;
}).bind('destroy', function () {
document.title = title;
});
}
},
managers: {
// 'DOM Element ID': { /* elFinder options of this DOM Element */ }
'elfinder': {}
}
});
</script>
</head>
<body>
<!-- Element where elFinder will be created (REQUIRED) -->
<div id="elfinder"></div>
</body>
</html>
The main thing added here to make it work is the getFileCallback function :
getFileCallback: function (file) {
window.opener.setValue(file.url);
window.opener.displayImage(file.url);
window.close();
},
This now works fine for my single image field and does not affect the CKEditor integration.

How can I add rows generated from object via twig to my datatable?

This is how I add rows to my datatables:
$(document).on('change', '.item-select', function() {
var optionValue = $(this).val();
var optionText = $('.item-select option[value="'+optionValue+'"]').text();
if (optionValue) {
table.row.add({
"id": 'test',
"name": 'test',
"type": 'test',
}).draw();
$('option', this).first().prop('selected', true);
}
});
I have an object columns :
array:3 [▼
"id" => ReflectionProperty {#7030 ▶}
"name" => ReflectionProperty {#7031 ▶}
"type" => ReflectionProperty {#7034 ▶}
]
Now I like to replace the hard coded fields with the fields from my object. This is my approach:
$(document).on('change', '.item-select', function() {
var optionValue = $(this).val();
var optionText = $('.item-select option[value="'+optionValue+'"]').text();
if (optionValue) {
table.row.add({
{% for key, value in columns %}
{ "{{ key }}": 'test'},
{% endfor %}
}).draw();
$('option', this).first().prop('selected', true);
}
});
The error in the console is this:
SyntaxError: expected property name, got '{'
You must remove curly brackets ...
$(document).on('change', '.item-select', function() {
var optionValue = $(this).val();
var optionText = $('.item-select option[value="'+optionValue+'"]').text();
if (optionValue) {
table.row.add({
{% for key, value in columns %}
"{{ key }}": 'test',
{% endfor %}
}).draw();
$('option', this).first().prop('selected', true);
}
});

Generate random hexadecimal colour in Twig

I want to generate random hex colors in Twig, to use it for example as a background in something like this:
{% for organization in organizations %}
{
value: {{ organization.value }},
color: "#F56954",
label: "{{ organization.name }}"
},
{% endfor %}
Any way to do it?
I suggest you to randomise a know set of data (in order to exclude no sense values) as follow:
{% for organization in organizations %}
{
value: {{ organization.value }},
color: "{{ random(['#H54924', '#F36252', '#F56954']) }}"
label: "{{ organization.name }}"
},
{% endfor %}
EDIT:
For pure random values, you can try the following approach:
{% set values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']%}
{% for organization in organizations %}
{
value: {{ organization.value }},
color: "#{{random(values)~ random(values)~ random(values)~ random(values)~ random(values)~ random(values) }}",
label: "{{ organization.name }}"
},
{% endfor %}
{{ '#' ~ random(values) ~ random(values) ~ random(values) ~ random(values) ~ random(values) ~ random(values) }}
Here a working example
Hope this help
class SomeUtil
{
/**
* #return string hex color string (RGB): #XXXXXX
*/
public static function randHexColor()
{
return sprintf("#%06s", dechex(rand(0, 256**3-1)));
}
/**
* #param int $rangeFrom [0..255]
* #param int $rangeTo [0..255]
* #return string hex color string (RGB): #XXXXXX
*/
public static function rangedRandHexColor($rangeFrom = null, $rangeTo = null)
{
$min = 0;
$max = 255;
if ($rangeFrom === null || !NumberUtil::inRange($rangeFrom, $min, $max)) {
$rangeFrom = $min;
}
if ($rangeTo === null || !NumberUtil::inRange($rangeTo, $min, $max)) {
$rangeTo = $max;
}
if ($rangeFrom === $min && $rangeTo === $max) {
return self::randHexColor();
} else {
return sprintf("#%02s", dechex(rand($rangeFrom, $rangeTo)))
.sprintf("%02s", dechex(rand($rangeFrom, $rangeTo)))
.sprintf("%02s", dechex(rand($rangeFrom, $rangeTo)));
}
}
}
In controller
$data['util'] = new SomeUtil();
return $this->renderView('AppBundle:Foo:bar.html.twig', $data);
In twig
{{ util.randHexColor() }}
or
{{ util.rangedRandHexColor(180, 220) }}
to exclude dark and LSD colors

Symfony2: use object to set route parameters

I have a route with 2 parameters:
BBBundle_blog_show:
pattern: /{id}/{slug}
defaults: { _controller: BloggerBlogBundle:Blog:show }
requirements:
_method: GET
id: \d+
Both params are properties of an object blog.
I would like to set up a custom mapper (route generator), so that I can write this:
{{ path('BBBundle_blog_show', {'blog': blog}) }}
instead of this:
{{ path('BBBundle_blog_show', {'id':blog.id, 'slug':blog.slug) }}
This is what I came up with eventually:
I implemented by own generator base class that looks for 'object' parameter and tries to get required parameters from that object.
//src/Blogger/BlogBundle/Resources/config/services.yml
parameters:
router.options.generator_base_class: Blogger\BlogBundle\Routing\Generator\UrlGenerator
//src/Blogger/BlogBundle/Routing/Generator/UrlGenerator.php
namespace Blogger\BlogBundle\Routing\Generator;
use Symfony\Component\Routing\Generator\UrlGenerator as BaseUrlGenerator;
use Doctrine\Common\Util\Inflector;
/**
* UrlGenerator generates URL based on a set of routes.
*
* #api
*/
class UrlGenerator extends BaseUrlGenerator
{
protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $absolute)
{
if (isset($parameters['object']) && is_object($parameters['object'])) {
$object = $parameters['object'];
$parameters = array_replace($this->context->getParameters(), $parameters);
$tparams = array_replace($defaults, $parameters);
$requiredParameters = array_diff_key(array_flip($variables), $tparams);
$parameters = $this->getParametersFromObject(array_flip($requiredParameters), $object);
}
return parent::doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $absolute);
}
protected function getParametersFromObject($keys, $object)
{
$parameters = array();
foreach ($keys as $key) {
$method = 'get' . Inflector::classify($key);
if (method_exists($object, $method)) {
$parameters[$key] = $object->$method();
}
}
return $parameters;
}
}
Now I can write: {{ path('BBBundle_blog_show', {'object': blog}) }} and it will get required parameters (id, slug) from object.
A while ago, I decided I was annoyed by being unable to pass objects as route parameters. I had to concern myself with knowledge of routes and the exact parameter values within templates and other things generating those routes.
I've build this bundle for symfony, which allows you to use and extend this ability (Symfony 2.7 and higher). Please take a look: https://github.com/iltar/http-bundle. It's also available on Packagist as iltar/http-bundle.
The best thing about this bundle is that you don't need to use another router object or generator. It's just turning on the bundle, adjusting the config to your needs if the defaults don't work out for your preferences and you're good to go. The readme should explain everything you need to know but here's a snippet:
Old style:
/**
* #Route("/profile/{user}/", name="app.view-profile")
*/
public function viewProfileAction(AppUser $user);
// php
$router->generate('app.view-profile', ['user' => $user->getId()]);
// twig
{{ path('app.view-profile', { 'user': user.id }) }}
{{ path('app.view-profile', { 'user': user.getid }) }}
{{ path('app.view-profile', { 'user': user.getId() }) }}
{{ path('app.view-profile', { 'user': user[id] }) }}
New style:
// php
$router->generate('app.view-profile', ['user' => $user]);
// twig
{{ path('app.view-profile', { 'user' : user }) }}

Resources