I want build a form using a block module in Drupal 8. I am aware of building the forms in Drupal 7 but the same seems to be different in Drupal 8.
Request anyone who has worked on drupal8 custom forms as block to help me.
Your question is very vague, as I don't know how much you already know about modules, forms and blocks in Drupal 8. So here is a small guide what to do, further information on how to do stuff in detail would be overkill for this answer.
1. Create a new module and enable it
Look here: Naming and placing your Drupal 8 module.
Basically you create the module folder and the module info yml file to let Drupal know about the module. Then you enable it using drush or the admin area in Drupal.
2. Create the form
Look here: Introduction to Form API.
under your_module/src/Form you create the form. More details in the link above.
3. Create the block and render the form
Look here: Create a custom block.
under your_module/src/Plugin/Block/ you create the block which will render the form.
The idea is basically (code updated with suggestion from Henrik):
$builtForm = \Drupal::formBuilder()->getForm('Drupal\your_module\Form\YourForm');
$renderArray['form'] = $builtForm;
return $renderArray;
Note: You don't need to wrap the $builtForm with the $renderArray, you can return just the $builtForm and be fine. I just personally like to do it that way, because often times I need to add something else to the final render array like some markup, cache settings or a library etc.
4. Place the block
Place the block in the desired region(s). Done.
To build a form using block module, you can easily use Webform module where you can add a form and display as a block.
If you mean to create a form programatically in the custom block, you can achieve that by creating two files shown below:
Form file (src/Form/DemoForm.php):
<?php
/**
* #file
* Contains \Drupal\demo\Form\DemoForm.
*/
namespace Drupal\demo\Form;
use Drupal\Core\Form\FormBase;
class DemoForm extends FormBase {
/**
* {#inheritdoc}.
*/
public function getFormId() {
return 'demo_form';
}
/**
* {#inheritdoc}.
*/
public function buildForm(array $form, array &$form_state) {
$form['email'] = array(
'#type' => 'email',
'#title' => $this->t('Your .com email address.')
);
$form['show'] = array(
'#type' => 'submit',
'#value' => $this->t('Submit'),
);
return $form;
}
/**
* {#inheritdoc}
*/
public function validateForm(array &$form, array &$form_state) {
$values = $form_state->getValues();
if (strpos($values['email'], '.com') === FALSE ) {
$form_state->setErrorByName('email', t('This is not a .com email address.'));
}
}
/**
* {#inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
drupal_set_message($this->t('Your email address is #email', array('#email' => $form_state['values']['email'])));
}
}
Source: Building a Drupal 8 Module: Blocks and Forms.
Block file (src/Plugin/Block/HelloBlock.php):
<?php
namespace Drupal\mymodule\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'Hello' Block.
*
* #Block(
* id = "form_block",
* admin_label = #Translation("My form"),
* category = #Translation("My Category"),
* )
*/
class HelloBlock extends BlockBase {
/**
* {#inheritdoc}
*/
public function build() {
$form = \Drupal::formBuilder()->getForm('\Drupal\mymodule\Form\HelloBlock');
//$form['#attached']['js'][] = drupal_get_path('module', 'example') . '/js/example.js';
//$form['#markup'] = $this->t('Custom text');
return $form;
}
}
Source: Create a custom block.
To add a form to the Block Configuration, see: Add a Form to the Block Configuration.
Here is a detailed summary of how to go about this:-
https://www.sitepoint.com/building-drupal-8-module-blocks-forms/
Following the above guide, you would add the completed form to the block build function, e.g.
class DemoBlock extends BlockBase {
/**
* {#inheritdoc}
*/
public function build() {
$form = \Drupal::formBuilder()->getForm('Drupal\demo\Form\DemoForm');
return $form;
}
}
Some more useful docs if you are new to Drupal 8 or need to dust off your knowledge:
https://www.drupal.org/docs/8/creating-custom-modules
https://www.drupal.org/docs/8/api/block-api
https://www.drupal.org/docs/8/api/form-api
Related
Context: On my Drupal 8 site I'm trying to add in a new section containing a view that lists out all forms. The user can then click on one of the form and it launches a download of all the messages sent through that form. However that section should only be visible if the user has created at least 1 form.
What I've tried: So far what I've discovered is that the contact forms are stored in the config table in the database, so I could theoretically run a query like "SELECT 1 FROM config WHERE name LIKE '%contact.form%'" or whatever the equivalent in D8 is. However, it seems like there has to be a quicker way to return either the presence of contact forms or the contact forms themselves programmatically.
Final question: How can I return either the contact forms themselves or at least a boolean representing the presence of contact forms programmatically?
Querying contact form entities
\Drupal::entityTypeManager()
->getStorage('contact_form')
->loadMultiple();
=> [
"feedback" => Drupal\contact\Entity\ContactForm {#3213},
"personal" => Drupal\contact\Entity\ContactForm {#3994},
]
Contact forms count
\Drupal::entityTypeManager()
->getStorage('contact_form')
->getQuery()
->count()
->execute();
=> 2
And on your final question; 1 way to approach this, is to subclass ContactFormListBuilder and override its default render() logic:
Routing YAML
my_module.custom_contact_form_list:
path: '/admin/foo/custom-contact-form-listing'
defaults:
_controller: '\Drupal\my_module\Controller\ContactFormListController::listing'
_title: 'A custom contact form listing view'
requirements:
_permission: 'administer contact forms'
Listing Controller
<?php
namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\my_module\CustomContactFormListBuilder;
/**
* Class ContactFormListController.
*
* #package Drupal\my_module\Controller
*/
class ContactFormListController extends ControllerBase {
/**
* Provides a custom listing page for contact forms.
*
* #return array
* A render array as expected by
* \Drupal\Core\Render\RendererInterface::render().
*/
public function listing() {
$definition = $this->entityTypeManager()->getDefinition('contact_form');
return $this
->entityTypeManager()
->createHandlerInstance(CustomContactFormListBuilder::class, $definition)
->render();
}
}
List Builder class
<?php
namespace Drupal\my_module;
use Drupal\contact\ContactFormListBuilder;
/**
* Class CustomContactFormListBuilder.
*
* #package Drupal\my_module
*/
class CustomContactFormListBuilder extends ContactFormListBuilder {
/**
* {#inheritdoc}
*/
public function render() {
return parent::render(); // TODO: Change the autogenerated stub
}
}
In the list field you can make a field editable by setting the attribute "editable" to "true" in the configureListFields action. Is it possible (with onboard sonata admin tools) to make a field editable that contains multiple values as in a one-to-many relation?
Example:
I have a list of pupils listed in the list view. Every pupil has multiple classes listet in the classes column of the pupils list view. Via click on the classes I want a popover open (like it works with a normale string) with a suggest field like you can have it in the edit view.
Using the properties like in the configFormFields action doesn't work:
$listMapper->add(
'classes',null, array(
'editable' => true,
'type' => 'sonata_type_model_autocomplete',
'multiple' => true,
'property' => 'name'
)
);
That snippet is written inside the PupilsAdmin class in the configureListFields action.
Is it possible or do I have to create a custom template?
The documentation doesn't point me in the right direction: https://sonata-project.org/bundles/admin/2-2/doc/reference/field_types.html
If i understand you right, you want to edit a one-to-many relation inline in a list view of sonata. As far as i know, thats only possible for simple types like text, int or choices and so on. They point it out on no 18. in your link
Theses types accept an editable parameter to edit the value from within the list action. This is currently limited to scalar types (text, integer, url...).
So related objects can not be in that list, merely their scalar properties. For all other things you have to write your own template ...
I don't know what you want to achieve with this suggested list, but for me it makes no sense to edit a one-to-many property in the list view like its done in the edit view.
You just need to create new type. Something like "entity"
'header_class' => 'col-lg-1',
'class' => Employee::class,
'editable' => true,
])
Next step is to override fixFieldDescription method in listBuilder and handle it
class EntityListBuilder extends ListBuilder
{
/**
* #var Registry
*/
private $doctrine;
/**
* #param AdminInterface $admin
* #param FieldDescriptionInterface $fieldDescription
*/
public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription)
{
parent::fixFieldDescription($admin, $fieldDescription);
if ($fieldDescription->getType() === 'entity') {
$class = $fieldDescription->getOption('class');
if (!$class) {
throw new RuntimeException("Type entity must contain 'class' argument");
}
$objects = $this->doctrine->getRepository($class)->findAll();
$choices = [];
foreach ($objects as $object) {
$choices[$object->getId()] = $object->__toString();
}
$fieldDescription->setOption('choices', $choices);
$fieldDescription->setType('choice');
}
}
/**
* #param Registry $doctrine
*/
public function setDoctrine(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
/**
* #param string $type
*
* #return string
*/
private function getTemplate($type)
{
return $this->templates[$type] ?? '';
}
Now, you have to override template for your "entity" type
{% extends '#SonataAdmin/CRUD/list_choice.html.twig' %}
{% set value = admin.id(value) %}
It's need to set already chosen value to select box
Okay, last thing is to add our type to xeditable types of Twig
Add it to OverrideServiceCompilerPass :
$definition = $container->getParameter('sonata.admin.twig.extension.x_editable_type_mapping');
$definition['entity'] = 'select';
$container->setParameter('sonata.admin.twig.extension.x_editable_type_mapping', $definition);
And the last one just match your type with template
templates:
types:
list:
...
entity: AppBundle:CRUD:list_entity.html.twig
Now you ready to edit it inline :)
Well, I have a fairly basic problem with Sonata Admin in my Symfony2 project.
I have a "products" list view with every product sold on my web store. On the top right "actions" menu, I have the default actions, with a single action named "Add new".
I just want to add more actions next to "Add new": custom actions like "remove promo prices from all products", or "remove all products evaluations".
I don't want a "batch" action, I want a "global" action leading to a custom DB query.
All I find in the doc is related to batch actions or "single line action". Is there a way to do what I want ?
Thank you for your help !
Create and configure a custom admin extension and override the configureActionButtons(AdminInterface $admin, $list, $action, $object) method to add custom actions:
use Sonata\AdminBundle\Admin\AdminExtension;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Route\RouteCollection;
class CustomGlobalActionsExtension extends AdminExtension
{
public function configureActionButtons(AdminInterface $admin, $list, $action, $object)
{
return array_merge($list, [
['template' => 'admin/custom_action.html.twig']
]);
}
public function configureRoutes(AdminInterface $admin, RouteCollection $collection)
{
$collection->add('custom_action', $admin->getRouterIdParameter().'/custom_action');
}
}
{# app/Resources/views/admin/custom_action.html.twig #}
<a class="btn btn-sm" href="{{ admin.generateObjectUrl('custom_action', object) }}">Custom Action</a>
See also https://sonata-project.org/bundles/admin/2-3/doc/cookbook/recipe_custom_action.html
Is syntax changed a little bit and it is important to call the parent method:
/**
* #param $action
* #param null|object $object
* #return array
*/
public function configureActionButtons($action, $object = null)
{
$buttonList = parent::configureActionButtons($action, $object);
$buttonList['create_custom'] = [
'template' => 'admin/action/button.html.twig'
];
unset($buttonList['not_wanted']);
return $buttonList;
}
In my Drupal template, I have a view loading multiple content types as fields. How can I apply css for certain types/add a class to the rows of a specific node type?
My solution was to override node--[contenttype].tpl.php and add the class after the print $classes
My approach to this would be to add a implementation of hook_preprocess_node(). As you have not stated the version of drupal you are using, I am going to presume you are using drupal 7.
https://api.drupal.org/api/drupal/modules%21node%21node.module/function/template_preprocess_node/7
/**
* Implements hook_preprocess_node().
*/
function hook_preprocess_node(&$variables) {
$n = $variables['node'];
if ($n->type != 'content_type_to_match') {
return;
}
$variables['classes_array'][] = 'example-class';
}
/**
* Implements hook_preprocess_HOOK().
*/
function freeman_theme_preprocess_views_view_unformatted(&$variables) {
foreach($variables['rows'] as $key => $row){
if(is_object($row['content']['#node'])){
$variables['rows'][$key]['attributes']->addClass('section-' .
$variables["rows"][$key]["content"]["#row"]->_entity->bundle());
}
}
}
Don't forget to replace views_view_unformatted with what you need
I'm writing a custom Drupal 7 module which will completely override the search page and search method for a website. Here is what I have so far:
/**
* Custom search.
*/
function mymodule_search_page() {
drupal_add_css('css/search.css');
// Perform a search (not important how)
$result = do_custom_search('foo');
return '<p>Results:</p>';
}
Now, as you can see, it's not complete. I don't know how to properly return structured HTML from this. How would I go about using Drupal's built-in template system to render the results?
you have to make use of drupal inbuilt functions. i hope you are looking for something like this http://api.drupal.org/api/drupal/includes!common.inc/function/drupal_render/7
This is what I ended up doing:
/**
* Implements hook_menu().
*/
function mymodule_search_menu() {
$items = array();
$items['search'] = array('page callback' => 'mymodule_search_page',
'access callback' => TRUE);
return $items;
}
/**
* Mymodule search page callback.
*/
function mymodule_search_page() {
$variables = array();
// Add stuff to $variables. This is the "context" of the file,
// e.g. if you add "foo" => "bar", variable $foo will have value
// "bar".
...
// This works together with `mymodule_search_theme'.
return theme('mymodule_search_foo', $variables);
}
/**
* Idea stolen from: http://api.drupal.org/comment/26824#comment-26824
*
* This will use the template file custompage.tpl.php in the same
* directory as this file.
*/
function mymodule_search_theme() {
return array ('mymodule_search_foo' =>
array('template' => 'custompage',
'arguments' => array()));
}
Hope this helps someone!