I have a custom Drupal 8 form that shows a list of users and a "check" button for each user.
When clicking a check button, the submit handler needs to figure out which users "check" button was clicked.
I've tried this the following way, but it always returns the id of the last element instead of the correct element.
Is this a bug in Drupal Core Form API?
Any other way to do this? I'm open to suggestions!
This is just an example. What I'm actually trying to do is show a list of users that belong to a specific 'company' node. In that list there is a 'remove from company' button for each user.
http://pastebin.com/us2YFcjr
<?php
namespace Drupal\form_multi_submit\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\Entity\User;
class MultiSubmitForm extends FormBase {
public function getFormId() {
return 'MultiSubmitForm';
}
public function buildForm(array $form, FormStateInterface $form_state) {
// Get all users from database
$ids = \Drupal::entityQuery('user')
->condition('status', 1)
->execute();
$users = User::loadMultiple($ids);
// Set form table header
$form['users'] = array (
'#type' => 'table',
'#header' => array('ID', 'Remove'),
);
// Loop through all users
foreach ($users as $user) {
// Show user ID
$form['users'][$user->id()]['id'] = array(
'#type' => 'label',
'#title' => $user->id(),
);
// Show button for each user
$form['users'][$user->id()]['removememberbutton']['dummyNode'] = array(
'#type' => 'submit',
'#value' => 'Check',
'#submit' => array([$this, 'removeMember']),
);
}
return $form;
}
// Submit handler
public function removeMember(array &$form, FormStateInterface $form_state) {
$userid = $form_state->getTriggeringElement()['#array_parents'][1];
drupal_set_message($userid, 'status');
}
public function validateForm(array &$form, FormStateInterface $form_state) {
// Nothing to do here.
}
public function submitForm(array &$form, FormStateInterface $form_state) {
// Nothing to do here.
}
}
Drupal considers buttons with the same #value as the same button, unless they have a different #name
So all I had to do was add a unique #name to my buttons to get this to work properly:
$form['users'][$user->id()]['dummyNode-' . $user->id()] = array(
'#type' => 'submit',
'#value' => 'Check',
'#name' => 'check_' . $user->id(),
'#submit' => array([$this, 'removeMember']),
);
https://www.drupal.org/node/1342066#comment-11904090
Related
i'm trying to show categories in search.twig using events for this i have create a module file to add event.
i am assuming after common/search controller is called the event will add categories to existing data variable and will send categories along all data from common/search controller. i have copied the code below
admin/controller/extention/module/cc_add_search_categories.php
public function install(){
$this->load->model('setting/event');
$this->model_setting_event->addEvent('ccaddsearchcategorise', 'catalog/controller/common/search/after', 'extension/module/cc_add_search_categories/addCategoriesToSearch');
}
public function uninstall(){
$this->load->model('setting/event');
$this->model_setting_event->deleteEventByCode('ccaddsearchcategorise');
}
and in
catalog/controller/extention/module/cc_add_search_categories.php
public function addCategoriesToSearch(&$route, &$data, &$output){
$this->load->model('catalog/category');
$this->load->language('extention/module/cc_add_search_category');
$data['categories'] = array('data');
$categories_1 = $this->model_catalog_category->getCategories(0);
foreach ($categories_1 as $category_1) {
$level_2_data = array();
$categories_2 = $this->model_catalog_category->getCategories($category_1['category_id']);
foreach ($categories_2 as $category_2) {
$level_3_data = array();
$categories_3 = $this->model_catalog_category->getCategories($category_2['category_id']);
foreach ($categories_3 as $category_3) {
$level_3_data[] = array(
'category_id' => $category_3['category_id'],
'name' => $category_3['name'],
);
}
$level_2_data[] = array(
'category_id' => $category_2['category_id'],
'name' => $category_2['name'],
'children' => $level_3_data
);
}
$data['categories'][] = array(
'category_id' => $category_1['category_id'],
'name' => $category_1['name'],
'children' => $level_2_data
);
}
}
I am new in Drupal world and I'm trying to use the Drupal From API to create a contact form with Ajax validation.
I'm facing 2 issues:
My following form (see below) works well but only when the user is connected as administrator. When I am not logged in as an administrator, it does not work.
I also created a custom Block to display my Form, unfortunately the block does not appear when I am logged in.
I try to follow this guide whitout success: https://panshul1410.blog/2018/07/15/drupal-8-ajax-validations-for-custom-form/
Here is the form I created:
<?php
namespace Drupal\dalcom_contact\Form;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class DalcomContactForm extends FormBase {
/**
* {#inheritdoc}
*/
public function getFormId() {
return 'dlcm_cform';
}
public function buildForm(array $form, FormStateInterface $form_state, $params = NULL) {
// Disable caching.
$form['#cache']['max-age'] = 0;
// Disable browser HTML5 validation.
$form['#attributes']['novalidate'] = 'novalidate';
// This will generate an anchor scroll to the form when submitting.
$form['#action'] = '#dlcm_cform';
$form['mail_visitor'] = [
'#type' => 'email',
'#placeholder' => t('E-mail*'),
'#description' => 'Your mail',
'#required' => TRUE,
'#ajax' => [
'callback' => 'Drupal\dalcom_contact\Form\DalcomContactForm::mailValidateCallback',
'effect' => 'fade',
'event' => 'change',
'progress' => [
'type' => 'throbber',
'message' => NULL,
],
],
];
$form['message_visitor'] = [
'#type' => 'textarea',
'#placeholder' => t('Message*'),
'#description' => 'Your message',
'#required' => TRUE,
'#ajax' => [
'callback' => 'Drupal\dalcom_contact\Form\DalcomContactForm::messValidateCallback',
'effect' => 'fade',
'event' => 'change',
'progress' => [
'type' => 'throbber',
'message' => NULL,
],
],
];
$form['accept_box'] = [
'#type' => 'checkbox',
'#title' => $this
->t('I accept the CME terms of use'),
'#required' => TRUE,
'#ajax' => [
'callback' => 'Drupal\dalcom_contact\Form\DalcomContactForm::acceptboxalidateCallback',
'effect' => 'fade',
'event' => 'change',
'progress' => [
'type' => 'throbber',
'message' => NULL,
],
],
];
$form['candidate_copy'] = [
'#type' => 'checkbox',
'#title' => t('Send me a copy of my message.'),
];
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = [
'#type' => 'button',
'#value' => $this->t('Send'),
'#ajax' => [
'event' => 'click',
'progress' => [
'type' => 'throbber',
'message' => 'Sending...',
],
],
];
$form['#theme'] = 'dalcom_contact_theme';
return $form;
}
public function submitForm(array &$form, FormStateInterface $form_state) {
foreach ($form_state->getValues() as $key => $value) {
drupal_set_message($key . ': ' . $value);
}
}
public function mailValidateCallback(array &$form, FormStateInterface $form_state) {
$ajax_response = new AjaxResponse();
if (!$form_state->getValue('mail_visitor') || empty($form_state->getValue('mail_visitor'))) {
$text = 'No email registered';
$color = 'red';
}
elseif (!filter_var($form_state->getValue('mail_visitor'), FILTER_VALIDATE_EMAIL)) {
$text = 'Invalid email address';
$color = 'red';
}
else {
$text = 'Valid mail';
$color = 'green';
}
$ajax_response->addCommand(new HtmlCommand('#edit-mail-visitor--description', $text));
$ajax_response->addCommand(new InvokeCommand('#edit-mail-visitor--description', 'css', ['color', $color]));
return $ajax_response;
}
public function messValidateCallback(array &$form, FormStateInterface $form_state) {
$ajax_response = new AjaxResponse();
if (!$form_state->getValue('message_visitor') || empty($form_state->getValue('message_visitor'))) {
$text = 'No messages written';
$color = 'red';
}
elseif (strlen($form_state->getValue('message_visitor')) < 6) {
$text = 'At least 7 characters';
$color = 'red';
}
else {
$text = 'Messages written';
$color = 'green';
}
$ajax_response->addCommand(new HtmlCommand('#edit-message-visitor--description', $text));
$ajax_response->addCommand(new InvokeCommand('#edit-message-visitor--description', 'css', ['color', $color]));
return $ajax_response;
}
public function acceptboxValidateCallback(array &$form, FormStateInterface $form_state) {
$ajax_response = new AjaxResponse();
if (empty($form_state->getValue('accept-box'))) {
$text = 'You must accept our termes of use to continue';
$color = 'red';
}
$ajax_response->addCommand(new HtmlCommand('#edit-accept-box--description', $text));
$ajax_response->addCommand(new InvokeCommand('#edit-accept-box--description', 'css', ['color', $color]));
return $ajax_response;
}
}
It works very well. But Ajax validation only works when I am connected. When I am not logged in as an administrator, it does not work.
Here is the block to display the form in my footer.
<?php
namespace Drupal\dalcom_contact_block\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormInterface;
/**
* Provides a 'dalcom contact' block.
*
* #Block(
* id = "dalcom_contact_block",
* admin_label = #Translation("Dalcom Contact Block"),
* category = #Translation("Block du formulaire Dalcom Contact")
* )
*/
class DalcomContactBlock extends BlockBase {
/**
* {#inheritdoc}
*/
public function build() {
$form = \Drupal::formBuilder()->getForm('\Drupal\dalcom_contact\Form\DalcomContactForm');
return $form;
}
}
As explain upper, the block does not appear when I am logged in. I have access to the form only through the path I defined in module.routing.yml. And when it appears (so when I'm not logged in), Ajax doesn't work on this block form either.
Does anyone have any idea what could cause this?
Please help me.
Edited:
Here is the module.routing.yml file
dalcom_contact.form:
path: '/contact-us.html'
defaults:
_title: 'Contact Us'
_form: 'Drupal\dalcom_contact\Form\DalcomContactForm'
requirements:
_permission: 'access content'
block.info.yml
name: Dalcom Contact Block
type: module
description: Block du formulaire Dalcom Contact.
core: 8.x
package: Custom
dependencies:
- node
- block
Updated:
I notice that when the form does not appear (when I am connected), it appears in its place
<span data-big-pipe-placeholder-id="…"></span>
I've done some research on this, but I can't get out of it. Maybe this will help you to know what's going on.
I finally found the solution on the Drupal forum. I put it here, maybe it will help in the future a novice who will be in my case.
What was missing was simply a list of essential javascripts for anonymous users. It seems that at one time it wasn't necessary, but now we have to add them otherwise they wouldn't be loaded for anonymous users.
So what I needed to do was add this to my theme.libraries.yml file
my_scripts:
version: VERSION
js:
js/scripts.js: {}
dependencies:
- core/jquery
- core/drupal.ajax
- core/drupal
- core/drupalSettings
- core/jquery.once
I am trying to make custom page template using these hooks in Drupal 7 but it shows blank page when i open in browser. Here is my code
/*
Implements hook_menu();
*/
function story_menu ()
{
$items['story/filters'] = array(
'title' => 'Search For stories',
'page callback' => 'story_filter_page',
'access arguments' => array('access content'),
);
return $items;
}
// Implements Page Callback
function story_filter_page ()
{
return theme('story_search_filter_page', array('title' => 'Testing'));
}
/*
Implements hook_theme();
*/
function story_theme($existing, $type, $theme, $path)
{
return array(
'story_search_filter_page' => array(
'variables' => array('title' => NULL),
'template' => 'custom-page',
),
);
}
I have created the template file : custom-page.tpl.php in my module directory.
I have figured it out why page is showing blank. basically story is my content type to so in my theme there is a tpl file name : page--story.tpl.php and that file was empty .. so that is why my pages showing me blank screen.
I'm learning how to use Zend Framework2. According to some tutorials available on the Net I've wrote some pieces of code . The most important tutorial for me is this one: https://github.com/psamatt/zf2-doctrine-example It covers most of the basics that i've planned to write. I've stuck on one problem that looks strange to me. On my summary page, that display all the records from DB I have a links to add new record, edit existing record, and delete record. Routing is covered by module.config.php:
'router' => array(
'routes' => array(
'incident' => array(
'type' => 'segment',
'options' => array(
'route' => '/incident[/][:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'Helpdesk\Controller\Incident',
'action' => 'index',
),
),
),
),
),
When I use a link to a new record (h.t.t.p://helpdesk/incident/add) everything works correctly. But when I use a link to edit my record (h.t.t.p://helpdesk/incident/edit/1 - where 1 is example record ID) I receive an error:
Zend\View\Renderer\PhpRenderer::render: Unable to render template "helpdesk/incident/edit"; resolver could not resolve to a file
This is my IncidentController.php:
<?php
namespace Helpdesk\Controller;
use Application\Controller\EntityUsingController;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject;
use Doctrine\ORM\EntityManager;
use Zend\View\Model\ViewModel;
use Helpdesk\Form\IncidentForm;
use Helpdesk\Entity\Incident;
class IncidentController extends EntityUsingController
{
/**
* Index action
*
*/
public function indexAction()
{
$em = $this->getEntityManager();
$incidents = $em->getRepository('Helpdesk\Entity\Incident')->findAll();
return new ViewModel(array(
'incidents' => $incidents
));
}
/**
* Edit action
*
*/
public function editAction()
{
$incident = new Incident();
if ($this->params('id') > 0) {
$incident = $this->getEntityManager()->getRepository('Helpdesk\Entity\Incident')->find($this->params('id'));
}
$form = new IncidentForm($this->getEntityManager());
$form->bind($incident);
$form->setHydrator(new DoctrineObject($this->getEntityManager(),'Helpdesk\Entity\Incident'));
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($incident->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$em = $this->getEntityManager();
$em->persist($incident);
$em->flush();
$this->flashMessenger()->addSuccessMessage('Incident saved');
// Redirect to list of incidents
return $this->redirect()->toRoute('incident');
}
}
return array(
'incident' => $incident,
'form' => $form,
);
}
/**
* Add action
*
*/
public function addAction()
{
return $this->editAction();
}
/**
* Delete action
*
*/
public function deleteAction()
{
$id = (int)$this->getEvent()->getRouteMatch()->getParam('id');
if (!$id) {
return $this->redirect()->toRoute('incident');
}
$request = $this->getRequest();
if ($request->isPost()) {
$del = $request->post()->get('del', 'No');
if ($del == 'Yes') {
$id = (int)$request->post()->get('id');
$incident = $this->getEntityManager()->find('Helpdesk\Entity\Incident', $id);
if ($incident) {
$this->getEntityManager()->remove($incident);
$this->getEntityManager()->flush();
}
}
// Redirect to list of incidents
return $this->redirect()->toRoute('default', array(
'controller' => 'incident',
'action' => 'index',
));
}
return array(
'id' => $id,
'incident' => $this->getEntityManager()->find('Helpdesk\Entity\Incident', $id)->getArrayCopy()
);
}
}
What is the difference between these two? Why one works fine, while the second one generates an error?
Thanks for your help
Smok.
Most likely helpdesk/incident/edit.phtml does not exist, while add action is rendering an existing helpdesk/incident/add.phtml.
You can reuse the existing helpdesk/incident/add.phtml or create a new one.
I have been told that we have to test also the functions created by Cake like add/delete...
If i have a function like this one, how can i test it if it doesn't have any return, redirect or even a view? ( i use ajax to execute it)
public function add() {
if ($this->request->is('post')) {
$this->Comment->create();
if ($this->Comment->save($this->request->data)) {
$this->Session->setFlash(__('The comment has been saved'));
} else {
$this->Session->setFlash(__('The comment could not be saved. Please, try again.'));
}
}
}
Thanks
Here's a sort of generic way to test it.
function testAdd() {
$Posts = $this->generate('Posts', array(
'components' => array(
'Session',
'RequestHandler' => array(
'isAjax'
)
)
));
// simulate ajax (if you can't mock the magic method, mock `is` instead
$Posts->RequestHandler
->expects($this->any())
->method('isAjax')
->will($this->returnValue(true));
// expect that it gets within the `->is('post')` block
$Posts->Session
->expects($this->once())
->method('setFlash');
$this->testAction('/posts/add', array(
'data' => array(
'Post' => array('name' => 'New Post')
)
));
// check for no redirect
$this->assertFalse(isset($this->headers['Location']));
// check for the ajax layout (you'll need to change
// this to check for something in your ajax layout)
$this->assertPattern('/<html/', $this->contents);
// check for empty view (I've never had an empty view but try it out)
$this->assertEqual('', $this->view);
}
public function add() {
$this->autoRender = false;
if ($this->request->is('post')) {
$this->Comment->create();
if ($this->Comment->save($this->request->data)) {
echo json_encode(array('status' => 'ok'));
} else {
echo json_encode(array('status' => 'fail'));
}
}
}