Symfony rendering templates from ajax - php

I have the following ajax I call:
$('.checkbox').click(function () {
$.ajax({
type:'POST',
url: '/loadProducts',
data: {},
success: function(response) {
$('.js-products').html(response);
}});
return false;
});
Now when I call my endpoint loadProducts, I wants to get products, render a product template(s) and return all of them.
In my endpoint I could do this:
/**
* #Route("/loadProducts", name="loadProducts")
*/
public function loadProducts() {
/* #var \AppBundle\Service\ProductService $productService */
$productService = $this->get('app.product');
$products = $productService->getProducts();
$productItems = [];
foreach ($products as $product) {
$productItems = $this->render('home/productItem.html.twig', [
'product' => $product
]);
}
$response = new Response();
$response->headers->set('Content-Type', 'application/json');
$response->setContent(json_encode($productItems));
return $response;
}
But that only renders one product. How could I return rendering multiple product.html.twig files? (apart from creating a new template that renders all)

You will get something like this. Your action will start to render products.html.twig which have a loop that renders multiple times product.html.twig
products.html.twig
{% extends base_template %}
{% block body %}
<div class="container">
<div class="row">
{% for product in products %}
<div class="col-md-3">
{% include 'shop/product.html.twig' %}
</div>
{% endfor %}
</div>
</div>
{% endblock %}
product.html.twig
<div class="product">
<img src="somesource.jpg">
<div class="description">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
</div>
</div>

Related

Twig not detecting the custom function

I have written a Twig custom function (in a Twig custom extension). I notice the template does not read the function and keeps throwing me the "Method Exists" error.
Was wondering if you have faced this before. Any ideas ?
The custom extension file; $post and $list are both objects:
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class ShowContentExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('ShowList', [$this, 'ShowList']),
new TwigFunction('ShowPost', [$this, 'ShowPost'])
];
}
public function ShowList($list) {
foreach ($list->post as $post) {
$this->ShowPost($post);
}
}
public function ShowPost($post) {
return !((count(array_keys($post->documents))) < 2 && array_key_exists('header', $post->documents));
}
}
and this is where they are being called:
{% if ShowList(list) %}
<h2 >{{ title }}</h2>
<div>
<div>
{% for post in list %}
{% if post|length > 0 %}
{% include ./_links.html.twig' with { 'list': list} %}
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
and :
{% if ShowPost(post) %}
<div>
<div>
<a href="{{ post.link }}">
<span>{{ post.title }}</span>
</a>
</div>
</div>
{% endif %}
and this is the screenshot of the error :
error screenshot

symfony 4.3 and Dropzone rename and show/delete existing files

I'm trying to implement an image upload for my application.
My issues:
- I tried all I could find online, but I am not able to rename the file before uploading. The file name on the server is always a hash or so.
- I think that the thumbnail creation is not working, because I have to add a css to have the image in 120px width.
- When removing one file it is not deleted.
Can anyone please explain how to realise that?
here my sources:
the template:
edit.html.twig
{% extends 'layout.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('includes/dropzone/min/basic.min.css') }}"/>
{% endblock %}
{% block tron %}
<h2>{{component}} edit - {{ entity.monitorId|default(entity.id) }}</h2>
{% endblock %}
{% block body %}
<div class="row">
<div class="col-sm-8">
{{ form_start(form, {'attr': {'id': 'infomonitore_form' } }) }}
{{ form_errors(form) }}
{{ form_row(form.detail) }}
{{ form_row(form.notizen) }}
<div class="form-group row">
<label class="col-form-label col-sm-2">
Files to add:
</label>
<div class="col-sm-10">
<div action="{{ oneup_uploader_endpoint('gallery', {id: entity.id}) }}" class="dropzone js-reference-dropzone" style="min-height: 100px">
</div>
</div>
</div>
<button type="submit" id="buttonSubmit" class="btn btn-primary">Update</button>
Show
{{ form_end(form) }}
</div>
<div class="col-sm-4">
</div>
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript" src="{{ asset('js/jquery-3.4.1.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('includes/dropzone/dropzone.js') }}"></script>
<script>
Dropzone.autoDiscover = false;
$(document).ready(function () {
var dropzone = new Dropzone('.js-reference-dropzone', {
autoProcessQueue: false,
dictMaxFilesExceeded: 'Only 5 Image can be uploaded',
acceptedFiles: 'image/*',
maxFilesize: 10, // in Mb
addRemoveLinks: true,
maxFiles: 5,
resizeWidth: 1920,
paramName: 'file',
renameFile: function (file) {
let newName = $('#InfomonitoreFormType_monitorId').val() + '-' + new Date().getTime() + '.' + file.name.split('.').pop();
file.name = newName;
return file.renameFilename = newName;
},
params: {
infomonitor: $('#InfomonitoreFormType_monitorId').val()
},
init: function () {
console.log(this.files);
{% for image in entity.images %}
var mockFile = {name: "{{ image.imageFile }}", size: 12345, dataURL: "{{ asset('locations/')~image.imageFile }}",accepted: true};
this.files.push(mockFile);
this.emit("addedfile", mockFile);
//this.createThumbnailFromUrl(mockFile, mockFile.dataURL);
this.emit("thumbnail", mockFile, mockFile.dataURL)
this.emit("complete", mockFile);
{% endfor %}
this.on("success", function (file, response) {
console.log("Success wurde aufgerufen");
//e.preventDefault();
$('#infomonitore_form').submit();
});
this.on("error", function (tmp) {
console.log('error');
console.log(tmp);
});
}
});
$('#buttonSubmit').click(function(e){
if(dropzone.getQueuedFiles().length > 0) {
e.preventDefault();
dropzone.processQueue();
}
});
});
</script>
{% endblock %}
and here my uploadListener (oneupUploader used):
class UploadListener
{
/* #var EntityManager $manager*/
private $manager;
/* #var InfomonitoreRepository $infomonitor */
private $infomonitor;
/**
* UploadListener constructor.
* #param EntityManager $manager
* #param InfomonitoreRepository $infomonitor
*/
public function __construct(EntityManager $manager, InfomonitoreRepository $infomonitor)
{
$this->manager = $manager;
$this->infomonitor = $infomonitor;
}
public function onUpload(PostPersistEvent $event)
{
$request = $event->getRequest();
/* #var Infomonitore $monitorId */
$monitorId = $this->infomonitor->find($request->get('infomonitor'));
/* #var File $file */
$file = $event->getFile();
$fileName = $monitorId->getMonitorId().'-'.time().'.'.$file->guessExtension();
$image = new Image();
$image->setImageFile(basename($file));
$image->setImageName($fileName);
$image->setInfomonitor($monitorId);
try {
$this->manager->persist($image);
$this->manager->flush();
} catch (ORMException $e) {
print_r($e);
}
// if everything went fine
$response = $event->getResponse();
$response['success'] = true;
return $response;
}
}
Thanks in advance.
you should use a custom namer in oneupuploaderbundle mapping configuration

Octobercms Component - Limit results on relation

I have a plugin with 2 components. One is for 'posts', and the other is for a 'profile'. The profile belongs to a user and hasMany 'posts'.
However when I access the relationship it loads every post. I just want to load 5 then paginate or lazy load, how can I do this?
Profile.php model
public $hasMany = [
'posts' => [
'Redstone\Membership\Models\Post',
'table' => 'redstone_membership_profiles_posts',
]
];
public $belongsTo = [
'user' => [
'Rainlab\User\Models\User',
]
];
Post.php model
public $belongsTo = [
'profile' => ['Redstone\Membership\Models\Profile',
'table' => 'redstone_membership_profiles_posts',
]
];
Profile.php component
protected function loadProfile()
{
$id = $this->property('profileUsername');
$profile = new UserProfile
$profile = $profile->where(['slug' => $id]);
$profile = $profile->first();
return $profile;
}
profile/default.htm - component view
{% set profile = __SELF__.profile %}
{% set posts = __SELF__.profile.posts %}
{% for post in posts %}
<div class="card">
<div class="card-header">
<div class="ml-2">
<div class="h5 m-0">{{ profile.title }}</div>
</div>
{{ post.published_at|date('M d') }}
</div>
<div class="card-body text-left">
{{ post.content|raw }}
</div>
</div>
{% else %}
<h1>This user has not made any posts.</h1>
{% endfor %}
Well you can either do something with the OctoberCMS pagination service or a php function or you could build a function through twig.
PHP using slice: This is assuming when you call $profile->posts you get a collection of posts. You could also add a query to the Url like example.com/profile?q=15 to change 5 to 10 15 etc I added an if statement to check to make sure the input is numeric and greater than 5.
protected function loadProfile()
{
$id = $this->property('profileUsername');
$profile = UserProfile::where(['slug' => $id])->first();
if (is_numeric(Input::get('q')) == true && Input::get('q') > 5) {
$profile->posts = $profile->posts->slice(0, Input::get('q'));
} else {
$profile->posts = $profile->posts->slice(0, 5);
}
return $profile;
}
Twig using slice: This is done very similar to the PHP way but done in the htm file instead.
PHP -
protected function getQuery()
{
if (is_numeric(Input::get('q')) == true && Input::get('q') > 5) {
return Input::get('q');
} else {
return 5;
}
}
Twig -
{% set query = __SELF__.getQuery %}
{% set profile = __SELF__.profile %}
{% set posts = __SELF__.profile.posts | slice(0, query) %}
{% for post in posts %}
<div class="card">
<div class="card-header">
<div class="ml-2">
<div class="h5 m-0">{{ profile.title }}</div>
</div>
{{ post.published_at|date('M d') }}
</div>
<div class="card-body text-left">
{{ post.content|raw }}
</div>
</div>
{% else %}
<h1>This user has not made any posts.</h1>
{% endfor %}

Why can't I pass this object correctly in Symfony2?

I am following the tutorial from here: https://www.digitalocean.com/community/tutorials/how-to-use-symfony2-to-perform-crud-operations-on-a-vps-part-1
Here is my controller:
public function indexAction()
{
$news = $this->getDoctrine()
->getRepository('FooNewsBundle:News')
->findAll();
if (!$news) {
throw $this->createNotFoundException('No news found');
}
$build['news'] = $news;
return $this->render('FooNewsBundle:Default:news_show_all.html.twig',$build);
}
Here is my view for news_show_all.html.twig
{% block body %}
<table>
{% for new in news %}
<h3>{{ new.Title }}</h3>
{% endfor %}
</table>
{% endblock %}
What I get is a blank page but when viewing source I get this:
<h3></h3>
<h3></h3>
Which is amazing since I have 3 items in my table and it only shows 2 and they are blank. Any ideas?

Symfony2 multiple forms in different JQuery UI tabs but single page

I'm facing a problem that I can summarize as it follows:
I have a TWIG template page like this (reg.html.twig):
{% extends "::base.html.twig" %}
{% block body %}
<ul class="tabs">
<li class="left">tab1</li>
<li class="left">tab2</li>
<li class="left">tab3</li>
<li class="right">tab4</li>
</ul>
<div class="tabs_container">
<div id="tab1" class="blocco-tab">
<form action="{{ path('AAA') }}" method="post" {{ form_enctype(form) }}>
<div id="name_field">
{{ form_row(form.name) }}
</div><!-- /name_field -->
<div id="address">
{{ form_row(form.addresses[0].road) }}
</div><!-- /address_field -->
</form>
</div>
<div id="tab2" class="blocco-tab">
<form action="{{ path('BBB') }}" method="post" {{ form_enctype(form) }}>
<div id="surname_field">
{{ form_row(form.surname) }}
</div><!-- /surname_field -->
</form>
</div>
</div> <!-- contenitore_tabs -->
{% endblock %}
Fields name, surname and addresses belong to a sample Symfony2 entity Person.
addresses is the first and only element of a collection of addresses (I need this as collection for other reasons)
The working JS file is:
jQuery(document).ready(function() {
$(".blocco-tab").hide();
$("ul.tabs li:first").addClass("active").show();
$(".blocco-tab:first").show();
$("ul.tabs li").click(function() {
$("ul.tabs li").removeClass("active");
$(this).addClass("active");
$(".blocco-tab").hide();
var activeTab = $(this).find("a").attr("href");
$(activeTab).fadeIn();
return false;
});
});
The Entity file:
class Person {
protected $name;
protected $surname;
protected $addresses;
public function __construct(){
$this->addresses = new ArrayCollection();
}
}
And in the DefaultController:
public function tab1Action(Request $request){
$person = new Person();
$address = new Address();
$addr_coll = new ArrayCollection();
$addr_coll->add($address);
$tab1_type = new Tab1Type();
$person->setAddresses($addr_coll);
$form = $this->createForm($tab1_type, $person);
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
/*ecc ecc ecc*/
}
public function tab2Action(Request $request){
$person = new Person();
$tab2_type = new Tab2Type();
$form = $this->createForm($tab2_type, $person);
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
/*ecc ecc ecc*/
}
Actually I took the way of having every FormType having all fields that I don't need rendered but put 'hidden' and 'property_path' => false, because I can't render only my desired fields cause the other ones will cause errors at runtime (they're null) , but I still get problems handling both cases in a joined way.
Putting every form in a different page (== different Route), with different Controllers, everything works fine, so it's not a problem related to basic use of symfony, It's the integration of N forms in a single page with JQuery UI that makes me cry.
Fixed that I have to use this tabs, how can I solve?
Do I have to make a single Action handling everything?
Do I have to make a single form?
Do I miss something?
Thanks in advance, I hope I've been clear in explaining my issue.
You just used the same variable for different forms
<div id="tab1" class="blocco-tab">
<form action="{{ path('AAA') }}" method="post" {{ form_enctype(**form1**) }}>
<div id="name_field">
{{ form_row(**form1**.name) }}
</div><!-- /name_field -->
<div id="address">
{{ form_row(**form1**.addresses[0].road) }}
</div><!-- /address_field -->
</form>
</div>
<div id="tab2" class="blocco-tab">
<form action="{{ path('BBB') }}" method="post" {{ form_enctype(**form2**) }}>
<div id="surname_field">
{{ form_row(**form2**.surname) }}
</div><!-- /surname_field -->
</form>
</div>
Try with single form
{% extends "::base.html.twig" %}
{% block body %}
<form action="{{ path('AAA') }}" method="post" {{ form_enctype(form) }}>
<ul class="tabs">
<li class="left">tab1</li>
<li class="left">tab2</li>
<li class="left">tab3</li>
<li class="right">tab4</li>
</ul>
<div class="tabs_container">
<div id="tab1" class="blocco-tab">
<div id="name_field">
{{ form_row(form.name) }}
</div><!-- /name_field -->
<div id="address">
{{ form_row(form.addresses[0].road) }}
</div><!-- /address_field -->
</div>
<div id="tab2" class="blocco-tab">
<div id="surname_field">
{{ form_row(form.surname) }}
</div><!-- /surname_field -->
</div>
</div> <!-- contenitore_tabs -->
</form>
{% endblock %}
Then you have in ocontroller aaaAction()
public function aaaAction(Request $request){
$person = new Person();
$address = new Address();
$addr_coll = new ArrayCollection();
$addr_coll->add($address);
$person->setAddresses($addr_coll);
$form = $this->createForm(new PersonType(), $person);
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
/*ecc ecc ecc*/
}
and class for form builder like
class PersonType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('name', null, array())
->add('surname', null, array())
->add('addresses', null, array())
;
}
public function getName()
{
return 'person';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\YourBundle\Entity\Person',
);
}
}
as I mentioned before I solved wrapping all tabs in a single form. Both of your solutions are ok, thank you for your time.
Linuxatico
In case anyone else has this problem, you can solve it like this (I am not sure if this is the most elegant solution but I think its better than making one big form for everything). This example mixes the profile and changePassword FOSUserBundle forms:
Inside your main show template (Profile:show.html.twig in my case):
{% if profileForm is defined %}
{% include 'MyBundle:Profile:edit.html.twig' with {'form':profileForm} %}
{% else %}
{% render 'MyBundle:Profile:edit' %}
{% endif %}
Repeat for changePassword:
{% if passwdForm is defined %}
{% include 'MyBundle:ChangePassword:changePassword.html.twig' with {'form':passwdForm} %}
{% else %}
{% render 'FOSUserBundle:ChangePassword:changePassword' %}
{% endif %}
In your controllers (add else):
if ($form->isValid()) {
.....
else {
return $this->container->get('templating')->renderResponse('FOSUserBundle:Profile:show.html.'.$this->container->getParameter('fos_user.template.engine'),
array('user' => $user, 'profileForm' => $form->createView()));
}
Add the profileForm and passwdForm accordingly. In my case the modified controllers were the ProfileController and ChangePasswordControllers (FOSUserBundle overrides).
As for your tabs, you can add javascript (or twig) to open the tab if any error is found.
Hope this helps :)

Categories