i have created a module in Drupal 8 for showing third party API data in a block
here is some data returned from that API
{"ObjectId":43,"ObjectName":"MEGA MELA","ObjectTitle":"Event Created by API","ObjectDescription":"NEW EVENT BY API","ObjectLabel":"","ObjectTypeId":33,"MaxFieldsExpected":5,"ObjectValueType":null,"ObjectControlType":"","IsDeleted":true,"CreatedDate":"2019-05-22T07:56:03.767","CreatedBy":null,"EditedDate":null,"EditedBy":null,"DeletedDate":null},{"ObjectId":44,"ObjectName":"Event x11","ObjectTitle":"Event Created by API","ObjectDescription":"NEW EVENT BY API","ObjectLabel":"","ObjectTypeId":33,"MaxFieldsExpected":5,"ObjectValueType":null,"ObjectControlType":"","IsDeleted":true,"CreatedDate":"2019-05-23T00:33:50.7","CreatedBy":null,"EditedDate":null,"EditedBy":null,"DeletedDate":null}]}
i have created a custom module to show some of this data in a block
this is my module directory hierarchy , module directory name is apihtml
-apihtml
-src
-Plugin
-Block
-rest.php
-apihtml.info.yml
as you can see there are just two files
apihtml.info.yml and rest.php
here is apihtml.info.yml content
name: Third Party Api Data with html
type: module
description: 'This is for showing rest data in ui with html'
package: Custom
version: 8.x
core: 8.x
dependencies:
- node
- block
and here is rest.php content
<?php
/**
* #file
*/
namespace Drupal\apihtml\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Component\Serialization\Json;
/**
* Creates a 'Foobar' Block
* #Block(
* id = "AntShow with html formatting",
* admin_label = #Translation("Ant Show & HTML"),
* )
*/
class rest extends BlockBase {
public function build() {
/** #var \GuzzleHttp\Client $client */
$client = \Drupal::service('http_client_factory')->fromOptions([
'base_uri' => 'http://myApiPath',
]);
$response = $client->get('objects/events');
$dec = Json::decode($response->getBody());
$items = [];
foreach ($dec as $d) {
foreach ($d as $ins) {
$items[] = $ins['ObjectName'] ;
}
}
return [
'#theme' => 'item_list',
'#items' => $items,
];
}
}
here by using array Key ObjecName i am able to show object name in block
here is the block OutPut
MEGA MELA
Event x11
but i want something more
i want to show this API returned data in the tabular form means the array key should be the header of the data value data will be in rows
like this
ObjectId | ObjectName | ObjectTitle | ObjectDescription | ObjectLabel | ObjectTypeId | MaxFieldsExpected | ObjectValueType | ObjectControlType | IsDeleted | CreatedDate | CreatedBy | EditedDate | EditedBy
43 | MEGA MELA | Event Created by API | NEW EVENT BY API | ..............................................................................
in this Drupal block all the data displayed has to be returned in build function in rest.php file
this is the code which shows data returned by api in tabular format
foreach ($dec as $d) {
?>
<table>
<?php foreach ($d as $ins) { ?>
<tr>
<?php
foreach ($ins as $key=>$value) {
echo "<td><h3>".$key."</h3></td>";
} ?>
</tr>
<tr>
<?php
foreach ($ins as $key=>$value) {
echo "<td><h3>".$value."</h3></td>";
} ?>
</tr><?php
} ?>
</table> <?php
}
But i am not getting how can i put this code in build function of rest.php to show tabular data in the block
Use custom Template:
1. change you return :
return [
'#theme' => 'item_list',
'#items' => $items,
];
return [
'#theme' => 'my_custom_template',
'#content' => $items,
];
in your apihtml.module us hook_theme
function apihtml_theme() {
return array(
'my_custom_template' =>[
'variables' => [
'content' => []
]
]);
}
and create a custom twig: templates/my-custom-template.html.twig
{% for data in content %}
...
{% endfor %}
You can create custom Twig template and display all your data as you want. You can read more about it here.
Related
This question already has an answer here:
How let Twig write the HTML output to a file, instead to a browser?
(1 answer)
Closed 1 year ago.
In this code below I have just generated in the browser a .xml page that shows a specific sitemap of the distributor route.
I just want to save this generated .xml in a file (in /public/sitemap folder).
what I have, and what I WANT TO USE, are only this two (like the render at the bottom of the Controller):
the urls array (see: inside the foreach)
the template of the xml file
I have already found a possible solution but is not pratical, and is not what I want. (Create new xml file and save in system using symfony2.8)
Controller:
/**
* #Route("/sitemap/sitemap.xml", name="distributor_sitemap", defaults={"_format"="xml"})
*/
public function getSitemap(Request $request) : Response
{
$urls = [];
$hostname = $request->getSchemeAndHttpHost();
// return an array of routes
$routes = $this->sitemapService->getThreeRoutesByIndexes('/distributor/');
foreach ($routes as $route){
$urls[] = array('loc' => $this->generateUrl($route));
}
// save locally the file
// -----------------------------
// ----- add the code HERE -----
// -----------------------------
// return response in XML format
$response = new Response(
$this->renderView('sitemap/sitemap.html.twig', array(
'urls' => $urls,
'hostname' => $hostname
))
);
$response->headers->set('Content-type', 'text/xml');
return $response;
}
Template:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
{% for url in urls %}
<url>{# check if hostname is not alreay in url#}
<loc>{%if url.loc|replace({hostname:''}) == url.loc%}{{hostname}}{{url.loc}}{%else%}{{url.loc}}{%endif%}</loc>
{% if url.lastmod is defined %}
<lastmod>{{url.lastmod}}</lastmod>
{% endif %}
{% if url.changefreq is defined %}
<changefreq>{{url.changefreq}}</changefreq>
{% endif %}
{% if url.priority is defined %}
<priority>{{url.priority}}</priority>
{% endif %}
{% if url.image is defined and url.image is not empty %}
<image:image>
<image:loc>{%if url.image.loc|replace({hostname:''}) == url.image.loc%}{{hostname}}{{url.image.loc}}{%else%}{{url.image.loc}}{%endif%}</image:loc>
<image:title>{{ url.image.title }}</image:title>
</image:image>
{% endif %}
</url>
{% endfor %}
The Browser result
So again the final result is to generate a .xml file in the /public/sitemap folder, and this file must contain the urls array informations disposed by the template, as it is showed in the browser. ( all the code that to this should be added in the Controller near the ---- add the code HERE ----)
Assign the content of your render template to a var, then write it in the file you want.
/**
* #Route("/sitemap/sitemap.xml", name="distributor_sitemap", defaults={"_format"="xml"})
*/
public function getSitemap(Request $request) : Response
{
$urls = [];
$hostname = $request->getSchemeAndHttpHost();
// return an array of routes
$routes = $this->sitemapService->getThreeRoutesByIndexes('/distributor/');
foreach ($routes as $route){
$urls[] = array('loc' => $this->generateUrl($route));
}
// save locally the file
$xmlContent = $this->renderView('sitemap/sitemap.html.twig', array(
'urls' => $urls,
'hostname' => $hostname
));
// Assuming the folder sitemap exists in `public`
$path = $this->getParameter('kernel.project_dir') . '/public/sitemap/sitemap.xml';
$fileSystem = new Filesystem();
$fileSystem->dumpFile($path, $xmlContent);
// -----------------------------
// ----- add the code HERE -----
// -----------------------------
// return response in XML format
$response = new Response($xmlContent);
$response->headers->set('Content-type', 'text/xml');
return $response;
}
I want to display a user picture (avatar) and some more fields in the menu.html.twig template.
I know that we can display these fields in a user.html.twig template.
{{ content.user_picture }}
{{ user.getDisplayName() }}
{{ content.field_name_user[0] }}
and etc.
But I want to display these fields in the menu.html.twig template.
As I think. we can make a variable in preprocess_block () and print the desired value.
Or if there is no necessary variable in the template - do it in the preprocessor of this template!
Help please make a decision on this issue. And what code you need to write.
It is better to write a pre-process and define a variable and insert the desired elements in it.
hook_preprocess_menu__menu_name(array &$variables) {
$userDetails = \Drupal\user\Entity\User::load(\Drupal::currentUser()->id());
/**
fetch the desired elements and pass to $variables eg:
**/
$variables['userPictureUrl'] = $userDetails->user_picture->entity->url();
}
You can use hook_preprocess_menu:
function YOURMODULE_preprocess_menu(&$variables)
{
$uid = \Drupal::currentUser()->id();
if($uid > 0)
{
// Load user
$user = User::load($uid);
$userName = $user->getUsername();
// If user have a picture, add it to variable
if(!$user->user_picture->isEmpty()){
$pictureUri = $user->user_picture->entity->getFileUri();
// Add style to picture
$userPicture = [
'#theme' => 'image_style',
'#style_name' => 'profile_picture',
'#uri' => $pictureUri,
];
}
// Set variables
$variables['MYMODULE'] = [
'profile_name' => $userName,
'profile_picture' => $userPicture,
'profile_id' => $userId
];
}
}
End show in your menu.html.twig file:
{{ YOURMODULE.profile_name }}
{{ YOURMODULE.profile_picture }}
{{ YOURMODULE.profile_id }}
I am using Drupal 8 and would like to customize how form elements are being displayed. Specifically, I don't like how uneditable, populated textfields are displayed as plain text. I would have it being displayed as an editable textfield (or have the text look like it is in an uneditable textfield). I have looked at various hook functions to try and achieve this but nothing seems to work.
I figure the best way to go about this is if I can render the form fields individually myself and then create a twig file that displays the individual fields as I would like them to be displayed. Here is what I would like the twig field to look like:
<div class="from">
{{ form.mail }}
</div>
<div class="message">
{{ form.message }}
</div>
<div class="actions">
{{ form.actions }}
</div>
In your .module file
function your_module_theme($existing, $type, $theme, $path) {
return [
'custom_theme' => [
'variables' => [
'form' => NULL
],
'render element' => 'form',
]
];
}
In your CustomForm.php file
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
[...your fields ...]
return $form
}
In your custom module templates directory
custom-theme.html.twig
{{ form.your_field }}
I'm trying to create a form which will add a new text box every time the 'Add new box' link got clicked.
I read through the following example.
http://symfony.com/doc/current/reference/forms/types/collection.html
Basically I was following the example from the book. But when the page is rendered and I click on the link nothing happens.
Any thoughts?
Thanks.
This is my controller.
public function createAction() {
$formBuilder = $this->createFormBuilder();
$formBuilder->add('emails', 'collection', array(
// each item in the array will be an "email" field
'type' => 'email',
'prototype' => true,
'allow_add' => true,
// these options are passed to each "email" type
'options' => array(
'required' => false,
'attr' => array('class' => 'email-box')
),
));
$form = $formBuilder->getForm();
return $this->render('AcmeRecordBundle:Form:create.html.twig', array(
'form' => $form->createView(),
));
}
This is the view.
<form action="..." method="POST" {{ form_enctype(form) }}>
{# store the prototype on the data-prototype attribute #}
<ul id="email-fields-list" data-prototype="{{ form_widget(form.emails.get('prototype')) | e }}">
{% for emailField in form.emails %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}
</ul>
Add another email
</form>
<script type="text/javascript">
// keep track of how many email fields have been rendered
var emailCount = '{{ form.emails | length }}';
jQuery(document).ready(function() {
jQuery('#add-another-email').click(function() {
var emailList = jQuery('#email-fields-list');
// grab the prototype template
var newWidget = emailList.attr('data-prototype');
// replace the "$$name$$" used in the id and name of the prototype
// with a number that's unique to our emails
// end name attribute looks like name="contact[emails][2]"
newWidget = newWidget.replace(/\$\$name\$\$/g, emailCount);
emailCount++;
// create a new list element and add it to our list
var newLi = jQuery('<li></li>').html(newWidget);
newLi.appendTo(jQuery('#email-fields-list'));
return false;
});
})
</script>
This problem can be solved by referring to the following link.
https://github.com/beberlei/AcmePizzaBundle
Here you will find the same functionality being implemented.
I've been through this too.
Answer and examples given to this question and the other question I found did not answer my problem either.
Here is how I did it, in some generic manner.
In generic, I mean, Any collection that I add to the form just need to follow the Form template loop (in a macro, for example) and that's all!
Using which convention
HTML is from Twitter Bootstrap 2.0.x
Javascript code is already in a $(document).ready();
Following Symfony 2.0.x tutorial
Using MopaBootstrapBundle
Form Type class
class OrderForm extends AbstractType
{
// ...
public function buildForm(FormBuilder $builder, array $options)
{
// ...
$builder
->add('sharingusers', 'collection', array(
'type' => new UserForm(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required'=> false
));
// ...
}
}
JavaScript
/* In the functions section out of document ready */
/**
* Add a new row in a form Collection
*
* Difference from source is that I use Bootstrap convention
* to get the part we are interrested in, the input tag itself and not
* create a new .collection-field block inside the original.
*
* Source: http://symfony.com/doc/current/cookbook/form/form_collections.html
*/
function addTagForm(collectionHolder, newBtn) {
var prototype = collectionHolder.attr('data-prototype');
var p = prototype.replace(/\$\$name\$\$/g, collectionHolder.children().length);
var newFormFromPrototype = $(p);
var buildup = newFormFromPrototype.find(".controls input");
var collectionField = $('<div class="collection-field"></div>').append(buildup);
newBtn.before(collectionField);
}
/* ********** */
$(document).ready(function(){
/* other initializations */
/**
* Form collection behavior
*
* Inspired, but refactored to be re-usable from Source defined below
*
* Source: http://symfony.com/doc/current/cookbook/form/form_collections.html
*/
var formCollectionObj = $('form .behavior-collection');
if(formCollectionObj.length >= 1){
console.log('run.js: document ready "form .behavior-collection" applied on '+formCollectionObj.length+' elements');
var addTagLink = $('<i class="icon-plus-sign"></i> Add');
var newBtn = $('<div class="collection-add"></div>').append(addTagLink);
formCollectionObj.append(newBtn);
addTagLink.on('click', function(e) {
e.preventDefault();
addTagForm(formCollectionObj, newBtn);
});
}
/* other initializations */
});
The form template
Trick here is that I would have had used the original {{ form_widget(form }} but I needed to add some specific to the view form and I could not make it shorter.
And I tried to edit only the targeted field and found out it was a bit complex
Here is how I did it:
{# All form elements prior to the targeted field #}
<div class="control-collection control-group">
<label class="control-label">{{ form_label(form.sharingusers) }}</label>
<div class="controls behavior-collection" data-prototype="{{ form_widget(form.sharingusers.get('prototype'))|escape }}">
{% for user in form.sharingusers %}
{{ form_row(user) }}
{% endfor %}
</div>
</div>
{{ form_rest(form) }}
I am using Symfony2 and Twig. I have a function (below) in my controller that returns a specific text. Is it possible to call that function directly from my template and change the {{text}} in my template to whatever the function returns, possibly via Ajax?
Here's my function:
public function generateCode($url) {
$url = $_SERVER['SERVER_NAME'] . '/embed/' . $url;
$return = '<iframe>'.$url.'</iframe>';
return $return;
}
Another controller function calls the function above and renders my template:
public function getCodeAction($url) {
$text = $this->generateCode($url);
return $this->render('MyMyBundle:User:code.html.twig', array('text' => $text));
}
In my template I am using:
{{ text }}
to display the value.
In Symfony 2.2, this was changed.
The render tag signature and arguments changed.
Before:
{% render 'BlogBundle:Post:list' with { 'limit': 2 }, { 'alt': BlogBundle:Post:error' } %}
After:
{% render controller('BlogBundle:Post:list', { 'limit': 2 }), { 'alt': 'BlogBundle:Post:error' } %}
or
{{ render(controller('BlogBundle:Post:list', { 'limit': 2 }), { 'alt': 'BlogBundle:Post:error'}) }}
Note: The function is the preferred way.
See https://github.com/symfony/symfony/blob/2.2/UPGRADE-2.2.md
You can use ajax if you have dynamic data, but as far as I can see from your brief info, you can always execute that controller function directly from your view:
{% render "MyMyBundle:User:generateCode" with { 'url': 'your url here' } %}
More Information on this available at:
http://symfony.com/doc/2.0/quick_tour/the_view.html, under Embedding other Controllers
For the record, in new versions you need to use the absolute URL:
{{ render url('my_route_id', {'param': value}) }}
{{ render(controller("AcmeDemoBundle:Demo:topArticles", {'num': 10})) }}
In Silex I solved it like this:
{{ render(url('route_name', {'param': value})) }}
If you do not have the route name, URL can be used:
{{ render(app.request.baseUrl ~ '/some-path/' ~ value) }}
If using URL we should always concat the baseUrl.
Symfony 2.6+
in twig:
{{ render(controller('AppBundle:PropertySearch:featuredProperties', {'limit': 15})) }}
controller:
/**
* featuredPropertiesAction
*
* #param Request $request
* #param int $limit
*
* #return Response
*/
public function featuredPropertiesAction(Request $request, $limit)
{
$search = $this->resultsHelper->featuredSearch($limit);
return $this->render('HASearchBundle::featured_properties.html.twig', [
'search' => $search,
]);
}