How to write a controller in Symfony2 to edit content with Jeditable - symfony

I'm using jeditable plugin for JavaScript and I want to implement it in my Symfony2 project. I want to edit a name with the plugin and that name to be edited in the database, too, not the change to be gone when I refresh the page, but in my case, it's gone. :(
I'm almost sure that the controller shouldn't be in that way and the problem is from it, but how exactly to write it? Here it is:
public function editCategoryAction(Request $request, $id)
{
$category = $this->repository->find($id);
$form = $this->createForm(new CategoryType(), $category);
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$this->em->persist($category);
$this->em->flush();
return $this->redirect($this->generateUrl('categories'));
}
}
return $this->render(
'AcmeBudgetTrackerBundle:Categories:categories.html.twig', array(
'form' => $form->createView()));
}
This is my template:
<a href="{{ path('edit_category', { 'id': cat.id}) }}">
<strong class="edit">
{{ cat.name }}
</strong>
</a>
<script>
var token = "{{form._token.vars.value}}";
var path = "{{ path('edit_category', { 'id': cat.id}) }}";
</script>
And this is in the .js file:
(function(){
$('.edit').editable(function (value, settings) {
var data = {};
data[this.id] = value;
data["_token"] = token;
console.log(path);
console.log(data);
$.post(path, data);
return(value);
}, {
indicator:'Saving...'
});
}) ();
The output in the console looks fine:
/BudgetTracker/web/app_dev.php/edit_category/52
Object {: "Edu", _token: "9d29860b59ccafbc265ea12346c91fa7e378cc97"}
but the problem is that nothing is posted to the database and when I hit refresh the change I made is gone.
Can you please help me to solve this? Thanks in advance! :)

I think you don't need to use the form component here, you only want to handle a string. So I'll explain a way to do.
JavaScript:
$('.edit').editable(path);
Controller:
public function editCategoryAction(Category $category)
{
//check if exists/valid
//get the text sent from jeditable
$name = $this->request->get('value');
$category->setName($name);
$em = $this->getDoctrine()->getManager();
$em->persist($category);
$em->flush();
//return the name value to jeditable so it can display it
return new Response($name);
}
Twig:
<strong class="edit">{{ cat.name }}</strong>
<script>
var path = "{{ path('edit_category', { 'id': cat.id}) }}";
</script>
jEditable sends edited text named as 'value', so you can get it in the controller. In your controller, you implicitly use paramconverter to get the category from the id in the URL. And that should be OK now.
Note that you can use FORJsRoutingBundle, if you want to avoid mixing twig with javascript in order to access path.

Related

EasyAdmin 3: How to display entities based on dql for CRUD index

I have an Entity named Page that can be a callToAction (boolean) and I would like to display the Page Entity with callToAction == false on one subMenu and the Page Entity with callToAction == true on another subMenu. I have a CRUD for the Page Entity. So the Dashboard would be something like that:
MenuItem::subMenu('Page', 'far fa-file-alt')->setSubItems([
MenuItem::linkToCrud('Page', 'fa fa-alt', Page::class),
MenuItem::linkToCrud('Call To Action', 'fa fa-file-alt', Page::class),
])
But I don't know where to put the dql to display the entities I want (callToAction true or false) and I don't even know if it's possible, but I know it was with Easy Admin 2, that's why I wonder.
I also would like that on the NEW Action, when you're on the Page with callToAction == true, when you create the new Entity Page from here, that the callToAction is set to true immediatly and the User doesn't even see the field. Still don't know if it's possible.
Thanks :)
EDIT: So i've found that I can use createIndexQueryBuilder() to display on the index exactly the entities, and it works well but I don't know how to call two different createIndexQueryBuilder depending of the subMenu we display. I tried doing a custom action and using createQueryBuilder but I don't have the params searchDto, etc:
public function configureActions(Actions $actions): Actions
{
$indexIsCallToAction = Action::new('indexIsCallToAction', 'Index Call To Action', 'fa fa-send')
->linkToCrudAction('indexIsCallToAction');
$actions->add(CRUD::PAGE_INDEX, $indexIsCallToAction);
return $actions;
//return parent::configureActions($actions); // TODO: Change the autogenerated stub
}
public function indexIsCallToAction(AdminContext $context,SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters){
$response = $this->get(EntityRepository::class)->createQueryBuilder($searchDto, $entityDto, $fields, $filters);
return $response;
}
So it doesn't work.
As a dashboard controller is an ordinary controller you can do something like this:
public function __construct(PageRepository $pageRepo)
{
$this->pageRepo = $pageRepo;
}
public function configureMenuItems(): iterable
{
$submenuItems = [];
if (null !== $pages = $this->pageRepo->findBy(["callToAction" => true ])) {
foreach ($pages as $page) {
$submenuItems[] = MenuItem::linkToCrud('Call To Action', 'fa fa-file-alt', Page::class);
}
}
yield MenuItem::subMenu('Page Submenu with callToAction', 'far fa-file-alt')->setSubItems($submenuItems);
$submenuItems = [];
if (null !== $pages = $this->pageRepo->findBy(["callToAction" => false ])) {
foreach ($pages as $page) {
$submenuItems[] = MenuItem::linkToCrud('Page', 'fa fa-alt', Page::class);
}
}
yield MenuItem::subMenu('Other Page Submenu', 'far fa-file-alt')->setSubItems($submenuItems);
}

Symfony 5 : how to create (in a database) with ajax and without form

I have this button and I'd like to make it so when I click on it, there's an ajax that goes directly into my create function in symfony but doesn't display any form (at this point I already have the informations I need). But I have no idea how to get the form that way.
I used to do
$livre = new Livre();
$livre->setUuid(Uuid::v4());
$form = $this->createForm(LivreType::class, $livre);
$form->handleRequest($request);
But obviously I can't use LivreType::class anymore cause I don't need the form.
I keep searching for information about this but I can't find anything
Any ideas?
You'll have multiple way of doing it.
I'm gonna show you a simple way to do it, and try to adapt or find a better way for doing it!
LivreController.php
/**
* #Route(path="/livre/create", methods={POST})
*/
public function createNewLivre(Request $request, EntityManagerInterface $em)
{
$json = $this->getJSON($request);
$newLivre = new Livre();
// Set to your new entity parameters...
$em->persist($newLivre);
$em->flush();
return $this->json([
'message' => 'A new Livre has been added.' // It could also be empty if you don't want to manage anything
]);
}
private function getJSON(Request $request)
{
$data = json_decode($request->getContent(), true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new HttpException(400, 'json invalid');
}
return $data;
}
script.js
let button = document.getElementById('livreCreatorButton');
button.addEventListener("click", e => {
e.preventDefault();
fetch('http://127.0.0.1:8000/livre/create', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: '{}',
})
});

Calling controller from twig

I'm currently discovering Symfony, and I have some trouble calling a function from one of my controllers.
> class CsvController extends Controller {
public function generateCsvAction(){
$conn=$this->get('database_connection')
$results=$conn->query("
SELECT *
FROM external_attribute;
")
$response=new StreamedResponse();
$response->setCallback(function() use($results)){
$handle=fopen("/home/basile/Documents/backend/src/CampaignBundle/Controller/test.csv","w+");
fputcsv($handle,array('test1
test2,
test3,
test4,
test5,
test6,
test7,
test8')
),';');
}
fclose($handle);
}
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
$response->headers->set('Content-Disposition','attachment; filename="test.csv"');
return $response;
}
I already set everything in my routing.yml :
export_csv:
defaults: { _controller: CampaignBundle:Controller:CsvController.php }
and now I want to call it from a button in a file named "index.html.twig" . I know we can render from the controller some variables and array, but here i directly want to call a function
If you have any kind of idea, it would be very welcomed !
To directly call a controller from your template :
{{ render(controller(
'NameOfYourBundle:NameOfYourClass:NameOfYourFunction'
)) }}
If controller needs parameters (eg) :
{{ render(controller(
'NameOfYourBundle:NameOfYourClass:NameOfYourFunction', { 'id': 3 }
)) }}
you can execute the function directly from your view:
{% render "YourBundle:Csv:generateCsv" with { 'url': 'export_csv' } %}
By JS and AJAX :
$("#button").on('click', function() {
$.ajax({
url: {{render(controller("YourBundle:Csv:generateCsv")) }},
success: function(result){
...
}});
});

autocomplete + elasticsearch + symfony2

I'm on symfony 2.2 + FoqElasticaBundle
I try to use autocomplete from a result query elasticsearch
<input type="text" class="span3" id="search" data-provide="typeahead" data-items="4" />
var subjects = ['PHP', 'MySQL', 'SQL', 'PostgreSQL', 'HTML', 'CSS', 'HTML5', 'CSS3', 'JSON'];
$('#search').typeahead({source: subjects})
autocomplete works like this, but...
How to get my elasticsearc url replace the subjects like this?
var subjects = "http://myhost:9200/_search?pretty=true";
I want that the source of my autocomplete is the result of my query elasticsearch
With typeahead you can use the data coming from the server like:
function search() {
$('#search').typeahead({
source: function (query, process) {
return $.get("{{path("url_to_fetch_from_server", {_format: "json"})}}", { "query" : query }, function (data) {
return process(data);
});
}
});
};
$(function(){
search();
});
As you can see the typeahead source function take 2 arguments.
query is the text type in the typeahead box. you need to pass this to the server
process is the callback.
The server should response back in JSON format. you can replace "{{path("url_to_fetch_from_server", {_format: "json"})}}" to your real url.
public function queryAction(){
$query = $this->getRequest()->query->get("query");
$gType = $this->container->get('foq_elastica.finder.search_db');
$results = $gType->find($query, 20);
$tools = [];
foreach($results as $result){
$tools[] = $result->getTool();
}
return new Response(json_encode($tools));
}

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