Using Kendo UI PHP wrappers with Symfony2 Twig Template - symfony

I am using Kendo UI as the frontend library for a Symfony2.3.4 app and trying to establish best practices for working with Controllers and the Twig templating engine. My question is specific to the Kendo PHP wrapper library which allow for easy instantiation of Kendo objects.
The problem is passing those objects to twig templates. For example:
// ItemController.php
public function addAction(){
$options = ['option1', 'option2', 'option3']
$dropdown = new \\Kendo\UI\DropDownList($options);
$select = $dropdown->render();
return $this->render('TestBundle:Titles:add.html.twig',
['options' = $options, 'dropdown' => $dropdown, 'select' => $select];
}
With this controller I can pass the Kendo object ($dropdown) into the template, but the actual rendered dropdown ($select) throws an array to string error. Now I can still render the object in the template by using the $options array:
// add.html.twig
<h1>Add Item</h1>
<!-- This renders the Kendo object -->
{{ dump(dropdown) }} <!-- object(Kendo\UI\DropDownList) (5) { etc -->
<!-- This throws an error -->
{{ dump(select) }}
<input id="dropdownlist" />
<script>
// this renders the dropdown
$("#dropdownlist").kendoDropDownList({
dataSource: {
data: [
{% for option in options %}
"{{ option }}",
{% endfor %}
]
}
});
</script>
There doesn't seem to be an efficient way of passing a string snippet of html into a template in the Symfony2/Twig system. I can still render the dropdown in the template (by using the $options array) but then what's the use of having the PHP Kendo object?
Also, say I have an owner for each item and I want to create a dropdown of owner options in the addAction of the ItemController? Can I have a selectAction in the OwnerController that returns html for an Owner dropdown or an array of Owners?
This type of layout seemed to have been dealt with by the slots of Symfony 1.* ... but it is not clear how to do it with Symfony2 and Twig.
What is the 'best practice' way of dealing with formbuilding in Symfony ... particularly when you are calling from more than Entity (Items & Owners) in one form?
And/or is there a way of properly using PHP wrappers for JS objects like those in Kendo UI within a standard Symfony2 controller?

Related

Passing Twig variables to Vue.js in Symfony

I am running Symfony 4 app with Vue.js enabled. Is there any good practice to send my data from Twig templates to Vue.js components? Currently I have a number of data items for example on my header component, and HTML element data section looks like this:
<header-nav class="headnav headnav-fixed z-depth-1"
logo="{{ asset('build/images/site_logo.png') }}"
username="{{ app.user.name }}"
logout-url="{{ path('logout') }}"
logout-title="{% trans %} Logout {% endtrans %}"
instruction-url="{{ path('system_instruction_download') }}"
instruction-title="{% trans %} Download instruction {% endtrans %}"
current-locale="{{ app.request.getLocale()|upper }}"
v-bind:locales="{{ locales|json_encode }}"
>
Let's say I have a number of different URL's and other stuff. What is the best way to send the data? Should I first prepare an array of URL's on my controller? Which controller should it be if I want to prepare some global variables which will be used on my header, so they shouldn't be locked only on one controller.
Assuming that you render multiple "vue applications", you can define global variables with
1) twig configuration
Documentation says:
"Twig allows to inject automatically one or more variables into all templates.
These global variables are defined in the twig.globals option inside the main Twig configuration file"
2) You could create abstract controller with function merging variables
// MyAbstractController.php
protected function getTwigVariables(array $vars) {
$globals = [];
// ... fill $globals with your stuff
return array_merge(['globalVar1' => ], $vars);
}
// TestController extends MyAbstractController
public function indexAction() {
//...
return $this->render('viewPath.html.twig', $this->getTwigVariables([
'specificVariable' => 'variableContent'
]));
}
You could also embed controllers inside your main twig.
You can create headerAction, footerAction etc. and create subrequest for this actions.
For storing variables you can also use script tags
// twig
<script id="serverJson" type="application/json">{{ jsonContent|json_encode()|raw }}</script>
// serverJson.js
const configElement = document.getElementById("serverJson");
export default JSON.parse(configElement.innerHTML);
// ViewTemplate.vue
import serverJson from "path-to-serverJson.js"

Add a variable to all rendering

I'm operating on a symfony application and I would like to set a new variable from all controllers to all rendering.
The reason is that something in my footer has become dynamic and in compliance with the MVC pattern I'd like to put the processing of this new data in my controllers.
What is the good way to do this with symfony ?
EDIT
I am not using symfony as a REST API, the Symfony server is only serving twig rendered templates as HTML.
Details on my case :
the current twig template has hard-coded titles for a form :
<div>
<h2>Today</h2>
<!-- today's inputs .... -->
</div>
<div>
<h2>Tomorrow</h2>
<!-- tomorrow's inputs .... -->
</div>
I'd like to give to variables to my views : $today and $tomorrow.
This way I'd be able to render day names instead of today or tomorrow.
<div>
<h2>{{ today }}</h2>
<!-- today's inputs .... -->
</div>
<div>
<h2>{{ tomorrow }}</h2>
<!-- tomorrow's inputs .... -->
</div>
For example if today is Tuesday, variables has to be assigned this way :
$today = "Tuesday" and $tomorrow = "Wednesday".
What's more
This is not a question about this specific case. I'd like to now if there is a way to pass a variable to all views without editing all controllers. As I see it, I'd put a parent action to all controller to generate this variable. I just wanted to know if this is the usual way.
I don't want to use ajax calls, and I don't want to put complex twig code inside my template. I want to handle this via controllers.
Please read official documentation about using global variables.
Off the top of my head - you can inject...
...scalar values from the global twig config
...scalar values from the service container parameters
...services (read php objects)
Or you can write Twig extension, like:
class DateExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('get_date', array($this, 'getDate'))
];
}
public function getDate($date)
{
// format it how you want
return (new \DateTime($date))->format('Y-m-d H:i:s');
}
}
And then use it in any template simply by:
<div>
<h2>{{ get_date('today') }}</h2>
<!-- today's inputs .... -->
</div>
<div>
<h2>{{ get_date('tomorrow') }}</h2>
<!-- tomorrow's inputs .... -->
</div>
About what kind of "dynamic" do you speak? If this dynamic goes from changing of some value stored in the data storage of your app, than, I belive, you could define controller action to retrive this data from, for example, your database and then call it via AJAX on every page load to obtain those value on client-side. Or maybe even use help of WebSocket's. But this is just assumption. If you really need help you should provide us more information about context of your task.

Service to get/set current entity

I have a Project entity, which has a controller defining many routes:
projects/1
projects/1/foo
projects/1/bar
I need a service to provide the current project. The use case is that I have dependencies in my base twig templates which need to know the current project. i.e. a dropdown project selector that is outside the context of the template the current controller is serving.
I've tried creating a service getting route info with $container->get('router.request_context');, however, that only provides a path. I don't want to have to parse the path string if I don't have to.
What is the most proper approach?
If I understood you correctly the solution for your problem is rendering/embedding controller. Of course it's simplest, yet somehow acceptable solution for rendering parts of html with some custom logic apart from current template.
You can read about rendering/embedding controllers.
Some snippets...
Define controller:action (obviously the logic in my example is pretty straight forward):
/**
* Generate select input field.
*
* #Route("/widget", name="project_widget")
* #Method("GET")
*/
public function widgetAction()
{
$repo = $this->getDoctrine()
->getManager()
->getRepository('AppBundle:Project');
// #NOTICE: Wee need master request info, because our widget
// is rendered and called as subrequest.
$masterRequest = $this->get('request_stack')->getMasterRequest();
// #NOTICE: You need second parameter as default value in case there is no id in masterRequest route.
$currentProjectId = $masterRequest->get('id', 0);
$currentProject = $repo->find($currentProjectId);
$projects = $repo->findAll();
return $this->render('project/widget.html.twig', [
'currentProject' => $currentProject,
'projects' => $projects,
]);
}
Then you need to create the template project/widget.html.twig for it:
<div class="widget_project_selection">
{% if projects is not empty %}
<select name="widget_project_selection" id="widget_project_selection">
<option value="">None</option>
{% for project in projects %}
<option value="{{ project.id }}"
{# #NOTICE: If this is current project, select it #}
{{- currentProject and project.id == currentProject.id
? 'selected="selected"'
: '' -}}
>
{{- project.title -}}
</option>
{% endfor %}
</select>
{% else %}
<span>{{ 'Sadly, no projects yet :('|trans }}</span>
{% endif %}
</div>
and at last (but not least) render it somewhere like in base.html.twig:
{{- render(controller('AppBundle:Project:widget')) -}}
I've created for you a Github repo as reference. It's a small Symfony app with similar setup. You can even run it if you like, don't forget about dependencies and database update thou. Entry point is /app_dev.php/project/
Take a look at widgetAction, widget template and example usage in base.html.twig.
EDIT: But that's not everything. You said you need a service. If for some reason rendering/embedding controller is not an option for you or you really whant to use a Service (as in Dependency Container) you can extend Twig and use the full power of services.
I've also implemented a Twig Filter as example to show you the real power of Twig Extensions in here and here (usage in templates).
Check out Twig Extension and Extending Twig for more info about Twig Extensions.
Also check out service.yml for service and extension definitions - if you are not using Symfony3.3+, there will be some additional work to do - defining service and extension directly.
In your controller, you can use type hinting to load the "current" entity via the route.
For example:
#myrouter.yml
current_project:
path: /projects/{project}
Separately, your controller...
//mycontroller.php
public function myControllerAction(Request $request, Project $project)
{
//$project is entity (assuming valid) loaded via the route above
return $this->render('mytemplate.twig', ['project' => $project]);
}

How to pass data from twig to Symfony2 controller using form

I would like to know if there is a method to pass some variables (text from textarea) from Twig to Symfony2 controller via form.
<form action="{{ path('admin_core_save') }}" method="post">
<div id="edit-template">
{% if template.getData() is defined %}
<textarea id="template">{{ template.getData() }}</textarea>
{% endif %}
</div>
<input type="submit" value="Save" />
</form>
When I press save button it is going to saveAction() method
public function saveAction(Request $request)
{
var_dump($request);
return new Response('abc');
}
but response does not contain any textarea text. Is there a way to get this there?
I know I can build form inside the controller and send it to Twig, but I would like to know if this way is possible.
you can access POST values through request object like:
$this->get('request')->request->get('name');
I'm sure you have to learn a bit about Symfony2 Form Component. You will find that symfony already has built-in system for rendering forms handling user data posted through them.
Answering your question. There is a Request object that provides you full access to all request data, including POST variables.
To access POST values use Request::get() method:
$request->get('variable_name');
To pass any data to the twig template, use TwigEngine::renderResponse
$this->container->get('templating')->renderResponse('AcmeDemoBundle:Demo:template.twig,html',
array(
'someVar' => 'some value'
)
);
This var will be avaliable in your template as:
{{ someVar }}

Using repository class methods inside twig

In a Symfony2.1 project, how to call custom entity functions inside template? To elaborate, think the following scenario; there are two entities with Many-To-Many relation: User and Category.
Doctrine2 have generated such methods:
$user->getCategories();
$category->getUsers();
Therefore, I can use these in twig such as:
{% for category in categories %}
<h2>{{ category.name }}</h2>
{% for user in category.users %}
{{ user.name }}
{% endfor %}
{% endfor %}
But how can I get users with custom functions? For example, I want to list users with some options and sorted by date like this:
{% for user in category.verifiedUsersSortedByDate %}
I wrote custom function for this inside UserRepository.php class and tried to add it into Category.php class to make it work. However I got the following error:
An exception has been thrown during the rendering of
a template ("Warning: Missing argument 1 for
Doctrine\ORM\EntityRepository::__construct(),
It's much cleaner to retrieve your verifiedUsersSortedByDate within the controller directly and then pass it to your template.
//...
$verifiedUsersSortedByDate = $this->getYourManager()->getVerifiedUsersSortedByDate();
return $this->render(
'Xxx:xxx.xxx.html.twig',
array('verifiedUsersSortedByDate' => $verifiedUsersSortedByDate)
);
You should be very carefull not to do extra work in your entities. As quoted in the doc, "An entity is a basic class that holds the data". Keep the work in your entities as basic as possible and apply all the "logic" within the entityManagers.
If you don't want get lost in your code, it's best to follow this kind of format, in order (from Entity to Template)
1 - Entity. (Holds the data)
2 - Entity Repositories. (Retrieve data from database, queries, etc...)
3 - Entity Managers (Perform crucial operations where you can use some functions from your repositories as well as other services.....All the logic is in here! So that's how we can judge if an application id good or not)
4 - Controller(takes request, return responses by most of the time rendering a template)
5 - Template (render only!)
You need to get the users inside your controller via repository
$em = $this->getDoctrine()->getEntityManager();
$verifiedusers = $em->getRepository('MYBundle:User')->getVerifiedUsers();
return array(
'verifiedusers' => $verifiedusers,
);
}

Resources