Drupal 8 Mailchimp Ajax Signup Block - ajax

I try to ajaxify the Drupal 8 Mailchimp SignUp Block but I stuck with the AjaxResponse.
This is my Form alter hook:
function mailchimp_ajax_form_form_alter(&$form, \Drupal\Core\Form\FormStateInterface &$form_state, $form_id) {
if ($form_id != 'mailchimp_signup_subscribe_block_form') {
return;
}
$form['submit']['#ajax'] = [
'callback' => 'mailchimp_ajax_form_callback',
'prevent' => 'click',
'progress' => array(
'type' => 'throbber',
'message' => t('Submitting data...')
)
];
}
This is my callback function:
function mailchimp_ajax_form_callback(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$response = new \Drupal\Core\Ajax\AjaxResponse();
$response->setContent('Response');
return $response;
}
But in Chrome console there is only an error:
Uncaught AjaxError:
An AJAX HTTP error occurred.
HTTP Result Code: 200
Debugging information follows.
Path: /node?ajax_form=1
StatusText: OK
ResponseText: Response
The Signup works, but my question is how can I get the mailchimp response and put it in a valid AjaxResponse?

First, the AjaxResponse object has a setData() method not setContent().
To actually subscribe someone to mailchimp through the Drupal module you can use the mailchimp_subscribe() function in the main mailchimp module.

My "working" solution now is:
alter the form block, add an ajax callback function, prevent click and adds some ajax progress visualization.
/**
* Implements hook_form_FORM_ID_alter()
*
* #param \Drupal\mailchimp_signup\Form\MailchimpSignupPageForm $form
* #param \Drupal\Core\Form\FormStateInterface $form_state
* #param $form_id
*/
function mailchimp_ajax_form_form_alter(&$form, \Drupal\Core\Form\FormStateInterface &$form_state, $form_id) {
if ($form_id != 'mailchimp_signup_subscribe_block_form') {
return;
}
$form['submit']['#ajax'] = [
'callback' => 'mailchimp_ajax_form_callback',
'prevent' => 'click',
'progress' => array(
'type' => 'throbber',
'message' => t('Submitting data...')
)
];
}
This is the Ajax Callback:
I create a new AjaxResponse Object and an array of the Drupal messages via drupal_get_messages(). Then some kind of hack to get the right message.
and add an ReplaceCommandObject which replaces the form with the Mailchimp message.
function mailchimp_ajax_form_callback(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$response = new \Drupal\Core\Ajax\AjaxResponse();
$messages = drupal_get_messages();
$message = $messages['status'][0];
if(!$message){
$message = $messages['warning'][0];
}
$response->addCommand(new \Drupal\Core\Ajax\ReplaceCommand('#mailchimp-signup-subscribe-block-form', $message));
return $response;
}
I think there are much more elegant ways to get this.
I still don't know why the submitForm Method of The Mailchimp Form is still invoked...

The AJAX option is committed to the module, you can find it here: https://www.drupal.org/project/mailchimp/issues/2721249
After applying the patch or using the version that includes it, you'll find a checkbox in the form configuration to enable the AJAX in it.

Related

Flash messages from Laravel not shown in Inertia after using redirect with

Basically I use SweetAlert2 to fire a toast whenever there is errors, single error or success messages.
It seems to work fine until I do a Redirect::route('auth.index')->with([...]), then the success or error/errors message won't fire at all.
I can open the VueDevTools and confirm that the error/success is visible though.
Works fine if I do redirect back to the same page with the errors/success message Redirect::back()->with([...]).
Everything works until I want to go to another view with the flash message. What am I missing or doing wrong? I've been searching and going through the Inertia docs and vue docs but can't find anything related other than the data sharing, which I've already done.
Thanks in advance if anyone got the time to help.
HandleInertiaRequests.php
/**
* Defines the props that are shared by default.
*
* #see https://inertiajs.com/shared-data
* #param \Illuminate\Http\Request $request
* #return array
*/
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'flash' => [
'message' => fn () => $request->session()->get('message'),
'type' => fn () => $request->session()->get('type'),
'title' => fn () => $request->session()->get('title'),
],
]);
}
PasswordController.php
/**
* Send a reset link to the given user.
*
* #param \App\Http\Requests\Password\EmailRequest $request
* #return \Illuminate\Http\RedirectResponse
*/
public function email(EmailRequest $request)
{
# Send reset link to user
$status = Password::sendResetLink(
$request->only('email')
);
# No leak if email exists or not.
if ($status === Password::RESET_LINK_SENT || $status === Password::INVALID_USER) {
return Redirect::route('auth.index')->with([
'message' => __($status),
'type' => 'success',
'title' => 'Success',
]);
}
# Error
return Redirect::back()->withErrors([__($status)]);
}
...
Layout.vue
<template>
<Swal :swalErrors="$page.props.errors" :swalFlash="$page.props.flash" />
</template>
<script>
import Swal from '../Component/Swal.vue';
</script>
Swal.vue
<template>
</template>
<script>
export default {
props: {
swalErrors: Object,
swalFlash: Object
},
watch: {
swalErrors: {
handler: function (errors) {
if (errors) {
this.toast(Object.values(errors).join(' '));
}
},
},
swalFlash: {
handler: function (flash) {
if (flash) {
this.toast(flash.message, flash.title, flash.type);
}
},
}
},
methods: {
toast: function (html, title, icon, timer) {
title = title || 'Error';
icon = icon || 'error';
timer = timer || 4000;
this.$swal.fire({
position: 'top-end',
toast: true,
icon: icon,
title: title,
html: html,
showClass: { popup: 'animate__animated animate__fadeInDown' },
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
timer: timer,
timerProgressBar: true,
showConfirmButton: false,
});
}
}
}
</script>
So basically the flash message is only one time use. Once you refresh or redirect to other page it will be remove. If you want to persist the session so that you can use it to the other page please refer to this Laravel Docs
A kind sir from reddit suggested using a persistent layout (from the Inertia docs). I did and it worked. enter link description here

Drupal 7 - Trying to add form to list view

sorry if this has been asked before, I looked around but haven't found this specific question on StackOverFlow.com.
I have a view called 'view-post-wall' which I'm trying to add the form that submits posts to this view called 'post' via ajax submit, though I haven't begun adding ajax yet.
My module's name is 'friendicate'
I don't understand what I'm missing here, I'm following a tutorial and have been unable to get past this issue for 2 days now.
I don't get any errors either.
Here is the module code in full
function _form_post_ajax_add() {
$form = array();
$form['title'] = array(
'#type' => 'textfield',
'#title' => 'Title of post',
);
$form['body'] = array(
'#type' => 'textarea',
'#title' => 'description',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit post',
);
return $form;
}
function post_ajax_preprocess_page(&$variables) {
//krumo($variables);
$arg = arg();
if($arg[0] == 'view-post-wall') {
$variables['page']['content']['system_main']['main']['#markup'] = drupal_render(drupal_get_form('_form_post_ajax_add'));
}
}
There are multiple ways to accomplish this, and I'll outline those methods below. Also, if nothing works from my suggestions below, it's possible that you have an invalid form function name. Im not sure if that throws off Drupal or not. The correct format for the function name should end in _form and contain the arguments $form and $form_state, like so:
_form_post_ajax_add_form($form, &$form_state) { ... }
Also, if you want to use a hook, Steff mentioned in a comment to your question that you'll need to use your module name in the function name.
friendicate_preprocess_page(&$variables) { ... }
Ok, now for a few ideas how to get the form on the page.
Block
You can create a custom block within your module, and then assign it to a region in admin/structure/blocks
<?php
/**
* Implements hook_block_info().
*/
function friendicate_block_info() {
$blocks = array();
$blocks['post_ajax'] = array(
'info' => t('Translation Set Links'),
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
/**
* Implements hook_block_view().
*/
function friendicate_block_view($delta = '') {
$block = array();
if ($delta == 'post_ajax') {
$form = drupal_get_form('_form_post_ajax_add_form');
$block['content'] = $form;
}
return $block;
}
Clear the cache and your block should appear in admin/structure/blocks
Views attachment before/after
You can add markup before and after a view using the Views hook hook_views_pre_render()
<?php
/**
* Implements hook_view_pre_render().
*/
function frendicate_views_pre_render(&$view) {
if($view->name == 'view_post_wall') { // the machine name of your view
$form = drupal_get_form('_form_post_ajax_add_form');
$view->attachment_before = render($form);
}
}
Or maybe use view post render
function friendicate_views_post_render(&$view, &$output, &$cache) {
//use the machine name of your view
if ($view->name == 'view_post_wall') {
$output .= drupal_render(drupal_get_form('_form_post_ajax_add'));
}
}

Yii2: ajax form validation on an ajax submitted form

I'm wondering if any Yii2 experts can help me understand how best to work with ajax forms combined with Yii ajax validation. I think I can explain the issue without taking you through all of my code.
I am working on a Promo Code entry form where the user enters their promo code into the form, the form is submit via ajax. We then perform a database lookup for the promo code details, validate the code and if the code validates, we want to display the registration form that is hidden on the page.
I have a custom validation function for the form field "code", which is the active field in a model scenario named "register".
class UserCode extends ActiveRecord
{
...
public function scenarios()
{
return [
'register' => ['code'],
];
}
public function rules()
{
return [
[['code'], 'required'],
[['code'], 'validateUserCode', 'on' => ['register']],
];
}
public function validateUserCode($attribute, $params)
{
// perform all my custom logic to determine if the code is valid
if ($code_invalid) {
$this->addError($attribute, 'Sorry, this code is invalid.');
}
}
...
}
Then in the controller, as the Yii2 Guide suggests, I trap this ajax validation with the following code:
public function actionValidate() {
$model = new UserCode(['scenario' => 'register']);
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
// no logic can be run after the above code b/c the form is submit with ajax
// and therefore always trapped in the Yii::$app->request->isAjax conditional
}
The above code all works fine and if I remove focus from the $form->field($model, 'code') field on my form, Yii's ajax validation kicks in and displays my custom error message based off of my custom validation logic.
My challenge arises when I go to submit the form. The form submission is also handled through ajax, and therefore the controller action always returns the result of the ActiveForm::validate($model); because if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) will get apply to both the ajax form validation AND on the form submit.
With the above approach, I am forced to return only the results of the ajax validation and not any json data that I may need for additional client side validation, such as displaying the registration form after a valid use code is submitted through the ajax form.
I realize that I can set 'enableAjaxValidation' => false on the ActiveForm and then return my own json data inside the if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) condition. If I do this, I am able to show the registration form because I have my own json data to work with.
Is there a way to have ajax validation on a form that is submitted with ajax? How could you trap the ajax validation separately from the ajax form submission to handle the two events in different manners?
Any suggestions or alternate approaches are GREATLY appreciated!
You should set up validationUrl with a different URL compared to the URL that you are submitting the form to. In this way you can have the validation function that would validate and return the return ActiveForm::validate($model); and the normal submit form that does something else.
You can read more about validationUrl here:
I have found solution :
Form :
<?php
$form = ActiveForm::begin(['id' => 'form-add-contact', 'enableAjaxValidation' => true, 'validationUrl' => Yii::$app->urlManager->createUrl('contacts/contacts/contact-validate')]);
?>
Submit Via Ajax :
<?php
$script = <<< JS
$(document).ready(function () {
$("#form-add-contact").on('beforeSubmit', function (event) {
event.preventDefault();
var form_data = new FormData($('#form-add-contact')[0]);
$.ajax({
url: $("#form-add-contact").attr('action'),
dataType: 'JSON',
cache: false,
contentType: false,
processData: false,
data: form_data, //$(this).serialize(),
type: 'post',
beforeSend: function() {
},
success: function(response){
toastr.success("",response.message);
},
complete: function() {
},
error: function (data) {
toastr.warning("","There may a error on uploading. Try again later");
}
});
return false;
});
});
JS;
$this->registerJs($script);
?>
Controller :
/*
* CREATE CONTACT FORM AJAX VALIDATION ACTION
*/
public function actionContactValidate() {
$model = new ContactsManagement();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
$model->company_id = Yii::$app->user->identity->company_id;
$model->created_at = time();
\Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
}
/**
* Quick Add Contact Action
* #param type $id
* #return type
*/
public function actionAddContact() {
$model = new ContactsManagement();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
$transaction = \Yii::$app->db->beginTransaction();
try {
if ($model->validate()) {
$flag = $model->save(false);
if ($flag == true) {
$transaction->commit();
return Json::encode(array( 'status' => 'success', 'type' => 'success', 'message' => 'Contact created successfully.'));
} else {
$transaction->rollBack();
}
} else {
return Json::encode(array('status' => 'warning', 'type' => 'warning', 'message' => 'Contact can not created.'));
}
} catch (Exception $ex) {
$transaction->rollBack();
}
}
return $this->renderAjax('_add_form', [
'model' => $model,
]);
}

Error with ajax and zf2 controller action

I was trying to use ajax to redirect to a controller action in zend framework 2 but the ajax is not responding rightly as well as I am not receiving the data alert.
Here is the ajax code:
$(".save_btn").click(function (){ //the class of submit button is save_btn
$.ajax({
type : 'POST',
url : '/template/addtemplate',
data : {'id':'test'},
success : function(data,status)
{
alert(data.message);
}
});
});
this is my controller code:
public function addtemplateAction()
{
$result = array('status' => 'error',
'message' => 'There was some error. Try again.'
);
$request = $this->getRequest();
if($request->isXmlHttpRequest()){
$data = $request->getPost();
if(isset($data['id']) && !empty($data['id'])){
return new JsonModel($result);
$result['status'] = 'success';
$result['message'] = 'We got the posted data successfully.';
}
}
return new JsonModel($result);
}
I have also added these particular things in my module.config.php file :
'strategies' => array (
'ViewJsonStrategy'
),
I think the problem lies in $request->isXmlHttpRequest() which returns blank.
Any help will be accepted..
Use any kind of developer tools. Chrome => f12 => Network tab and check your response

Laravel 4 Basic Auth custom error

I'm using the 'HTTP Basic Authentication' feature of laravel. I want to customize the error message which is generated from laravel if the entered credentials are wrong.
Is it possible to catch the 401 Error which is generated when HTTP Auth fails?
Hope you can help me.
Regards
Basic Auth
Try to capture 401 error and return cusom view?!
App::error(function($exception, $code)
{
switch ($code)
{
case 401:
return Response::view('errors.403', array(), 401);
case 403:
return Response::view('errors.403', array(), 403);
case 404:
return Response::view('errors.404', array(), 404);
case 500:
return Response::view('errors.500', array(), 500);
default:
return Response::view('errors.default', array(), $code);
}
});
Using Auth library
I think, code is pretty straightforward and self explaining.
Just to note, $errors variable is of type MessageBag and is available in views even if you don't set it explicitly! Which is great! :)
I used simple routing, place it into your controllers
app/routes.php
Route::get('auth', function()
{
$creds = array(
'email' => Input::get('email'),
'password' => Input::get('password'),
);
if ( ! Auth::attempt($creds))
{
$errors = new MessageBag;
$errors->add('login', trans("Username and/or password invalid."));
return Redirect::to('/')->withErrors($errors);
}
return Redirect::to('/protected/area');
});
Route::get('/', function(){
return View::make('hello');
});
// app/views/hello.php
#if($errors->has('login'))
{{ $errors->first('login') }}
#endif
Here's how I did it:
Route::filter('auth.basic', function()
{
$message = [
"error" => [
"code" => 401,
"message" => "Invalid Credentials"
]
];
$headers = ['WWW-Authenticate' => 'Basic'];
$response = Auth::basic();
if (!is_null($response)) {
return Response::json($message, 401, $headers);
}
});
If you look in Illuminate\Auth\Guard you'll find the basic method that's called by Auth::basic(). It either returns null or a Response object via the getBasicResponse method.
/**
* Attempt to authenticate using HTTP Basic Auth.
*
* #param string $field
* #param \Symfony\Component\HttpFoundation\Request $request
* #return \Symfony\Component\HttpFoundation\Response|null
*/
public function basic($field = 'email', Request $request = null)
{
if ($this->check()) return;
$request = $request ?: $this->getRequest();
// If a username is set on the HTTP basic request, we will return out without
// interrupting the request lifecycle. Otherwise, we'll need to generate a
// request indicating that the given credentials were invalid for login.
if ($this->attemptBasic($request, $field)) return;
return $this->getBasicResponse();
}
Here's getBasicResponse:
/**
* Get the response for basic authentication.
*
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function getBasicResponse()
{
$headers = array('WWW-Authenticate' => 'Basic');
return new Response('Invalid credentials.', 401, $headers);
}
Here we finally have our 'Invalid credentials.' text that we're looking to change. We see it's just returning an instance of a Symphony response with a 401 status code and the Basic Auth header and null in all other occasions. So, we'll just check for a non-null result and if we get one, return our new response as shown above.
Also, if you want it to actually be stateless you should use:
Auth::onceBasic()
I don't know how future proof this method is, but it works as of Laravel 4.1.
Final results once again:
Route::filter('auth.basic', function()
{
$message = [
"error" => [
"code" => 401,
"message" => "Invalid Credentials"
]
];
$headers = ['WWW-Authenticate' => 'Basic'];
$response = Auth::onceBasic();
if (!is_null($response)) {
return Response::json($message, 401, $headers);
}
});

Resources