I created a form which collects data to either launch an external process (bash script) or to make a database query (using queryBuilder). Since both procedure could take some time, I would like to update the submit button, as soon as it is clicked, with a message (like 'Processing..., please wait') or an icon (like the animated icons of FontAwesome). Here is the function I set in my Controller class (Symfony2):
public function inputAction(Request $request)
{
$form = $this->createForm(new InputType());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
$data = $form->getData();
$cmd = '... my command using $data and taking some time ... ';
$process = new Process($cmd);
$process->setTimeout(120);
$process->run();
// Alternative DB query
// $qb = $this->getDoctrine()->getManager()
// ->createQueryBuilder();
// $query = $qb->select('COUNT(p)')
// ->add('from','myBundle:Ec p')
// ->add('where', 'p.ec LIKE :ec')
// ->setParameter('ec', $data['..'].'%')
// ->getQuery();
//
// $result = $query->getSingleScalarResult();
return $this->redirectToRoute('_input');
}
return $this->render('myBundle:Default:form.html.twig', array(
'form' => $form->createView(),
));
}
I thus added a JQuery call in my template
{% extends 'myBundle:Default:input.html.twig' %}
{% form_theme form 'bootstrap_3_layout.html.twig' %}
{% block form %}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
$(function() {
$('.btn-default').on('click', function() {
$(this).prepend('<i class="fa fa-refresh fa-spin"></i> ');
});
});
</script>
{%endblock %}
But the message or the icon is not displayed when the button is clicked. It is only displayed if I stop the process in the browser. I also tried the 'submit' event, with no more success, while 'mouseover' is perfectly working.
EDIT 1
Following the advice from #Micha, when I modified JS like that:
$(function() {
$('.btn-default').attr('prepend', 'false')
$('.btn-default').on('click', function(e) {
if($('.btn-default').attr('prepend') == 'false'){
e.preventDefault();
$('.btn-default').attr('prepend','true');
$(this).prepend('<i class="fa fa-refresh fa-spin"></i> ');
$(this).trigger('click');
}
});
});
I see that the DOM is updated and the query (or the process) launched, but the prepend text or icon is not actually displayed in the browser.
How about preventDefault() and then submit() the form from in the click function?
<script>
$(function() {
$('.btn-default').on('click', function(event) {
event.PreventDefault();
$(this).prepend('<i class="fa fa-refresh fa-spin"></i> ');
$(#my-form).submit();
});
});
</script>
Hope this helps.
The problem I was facing is apparently only occurring with Safari. My original code (and most of the proposed solutions by #Micha and #chris-rogers) are perfectly working in Chrome and Firefox.
Related
I want to modify Django admin interface.
There is delete button already has a confirmation page. But I want to add this confirmation to save or change buttons. Actually not exactly the same delete button.
I want to change default admin buttons like this JS or I want to add JS to admin buttons.
<input type="submit" onclick="linkSubmit('http://www.google.com')" value="Submit">
<p id="demo"></p>
<script>
function linkSubmit(link) {
let text = "Press a button!\nEither OK or Cancel.";
if (confirm(text) == true) {
window.location.href = link;
} else {
}
document.getElementById("demo").innerHTML = text;
}
</script>
We found the solution in the files of the delete command. We took copies of the files confirm to the delete function and connected them to the confirm button.
We still can't give it as a alert. Gives confirmation on a another page.
Assuming there is already some type of event listener for the button I would add my own custom function as an additional listener for the on click event. Then I would put in my if(confirm) logic and call event.stopImmediatePropagation() as needed to prevent the original functionality from occuring.
Create templates/admin/change_form.html in your project:
{% extends "admin/change_form.html" %}
{% block admin_change_form_document_ready %}{{ block.super }}
<script id="django-admin-form-change-constants"
data-model-name="{{ opts.model_name }}">
let modelName = document.getElementById('django-admin-form-change-constants').dataset.modelName;
let form = document.getElementById(modelName + '_form');
form.addEventListener('submit', (event) => {
let text = "Press a button!\nEither OK or Cancel.";
if (!confirm(text)) {
event.preventDefault();
}
});
</script>
{% endblock %}
Set DIRS in myproject/settings.py to point to your project's templates directory:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
...
}
]
References:
https://docs.djangoproject.com/en/4.1/ref/contrib/admin/#overriding-admin-templates
https://docs.djangoproject.com/en/4.1/howto/overriding-templates/#overriding-from-the-project-s-templates-directory
https://github.com/django/django/blob/4.1/django/contrib/admin/templates/admin/change_form.html#L66-L74
https://github.com/django/django/blob/4.1/django/contrib/admin/static/admin/js/change_form.js#L5
My code right now works perfectly fine!
It updates the user status(disable, enable account) without refreshing the page.
Right now if I click to the red button Desactiver it will become green Activer and the same thing in reverse..
What I wanna do is: When I Click to the button the status also get updated (which is in a different <td> )
In my axios code, I tried to get the status element using
const statusText= this.querySelector('span.status11');
and update it using
statusText.textContent=response.data.current;
The problem is that using this.querySelector return null because it is in a different <td>
and this points to a.js-status because on my code i did
document.querySelectorAll('a.js-status').forEach(function(link)
Here is the image
Here is my current code that updates ONLY the button (while i wanna update the button + the status <td>
Controller function:
/**
* #Route("/{id}/status", name="user_status", methods={"GET","POST"})
* #param Request $request
* #param User $user
* #return Response
*/
public function updateStatus(User $user): Response
{
if($user->getStatus()==true){
$user->setStatus(false);
$current="Desactiver";
} else {
$user->setStatus(true);
$current="Activer";
}
$this->getDoctrine()->getManager()->flush();
return $this->json([
'code'=>200,
'message'=>'the Status of is updated',
'current'=>$current
],200);
}
html code :
{% if user.status == 0 %}
Compte est <span class="status11" >Desactiver</span>
{% else %}
Compte est <span class="status11" >Activer</span>
{% endif %}
</td>
<td>
{% if user.status == 1 %}
<button class="btn btn-danger btn-sm">Desactiver</button>
{% else %}
<button class="btn btn-success btn-sm">Activer</button>
{% endif %}
</td>
My axios/javascript code :
{% block javascripts %}
<script src="https://unpkg.com/axios/dist/axios.min.js"> </script>
<script>
function onClickBtnLike(event){
event.preventDefault();
const url = this.href;
const btn= this.querySelector('button');
axios.get(url).then(function(response){
if(btn.classList.contains('btn-danger')) {
btn.classList.replace('btn-danger','btn-success');
btn.textContent="Activer";
}else{
btn.classList.replace('btn-success','btn-danger');
btn.textContent="Desactiver";
}
});
}
document.querySelectorAll('a.js-status').forEach(function(link){
link.addEventListener('click',onClickBtnLike);
})
</script>
{% endblock %}
You can traverse up from the button to the containing row and search for your status span from there:
const status = this.closest('tr').querySelector('.status11');
status.textContent = (status.textContent == "Activer" ? "Desactiver" : "Activer");
You could also target the previous td directly by using this.parentElement.previousElementSibling but the former is clearer and gives you some leeway if you want to change the html structure.
I'm creating a social network site using Laravel. I have a page that load all the posts created by users the currentUser follows. I have a comment section on each post. I want a user to be able to comment on any post without the page reloading so the user doesn't have to re-scroll through the page.
I have everything working fine without ajax (minus the reloading page). I'm able to post comments, the page reloads and the new comment is displayed. However, when I try to use Ajax I've been running into problems.
Here is my code.
Here is the view of the comment-box. It contains a section where I loop through each comment and display them. At the end is the type field so a user can post a new comment:
<div class="comment-box-container ajax-refresh">
<div class="comment-box">
#if ($type->comments)
#foreach ($type->comments as $comment)
<div class="user-comment-box">
<div class="user-comment">
<p class="comment">
<!-- starts off with users name in blue followed by their comment-->
<span class="tag-user">{{ $comment->owner->first_name }} {{ $comment->owner->last_name }} </span>{{ $comment->body }}
</p>
<!-- Show when the user posted comments-->
<div class="com-details">
<div class="com-time-container">
{{ $comment->created_at->diffForHumans() }} ยท
</div>
</div>
</div><!--user-comment end-->
</div><!--user-comment-box end-->
#endforeach
#endif
<!--type box-->
<div class="type-comment">
<div class="type-box">
{{ Form::open(['data-remote', 'route' => ['commentPost', $id], 'class' => 'comments_create-form']) }}
{{ Form::hidden('user_id', $currentUser->id) }}
{{ Form::hidden($idType, $id) }}
{{--{{ Form::hidden('user_id', $currentUser->id) }}--}}
{{ Form::textarea('body', null, ['class' =>'type-box d-light-solid-bg', 'placeholder' => 'Write a comment...', 'rows' => '1']) }}
{{ Form::close() }}
</div><!--type-box end-->
</div><!--type-comment-->
</div><!--comment-box end-->
The user submit the form for the comment type box by pressing the "enter/return" key. Here is the JS for that
<script>
$('.comments_create-form').on('keydown', function(e) {
if (e.keyCode == 13) {
e.preventDefault();
$(this).submit();
}
});
</script>
Here is my Ajax
(function(){
$('form[data-remote]').on('submit', function(e){
var form = $(this);
var method = form.find('input[name="_method"]').val() || 'POST';
var url = form.prop('action');
$.ajax({
type: method,
url: url,
data: form.serialize(),
success: function(data) {
var tmp = $('<div>');
tmp.html(data);
$('.ajax-refresh').html(tmp.find('.ajax-refresh').html());
$('.type-box').html(tmp.find('.type-box').html());
tmp.destroy();
}
});
e.preventDefault();
});
})();
I'm running into a few problems with this code. The comment gets displayed on ever single post until I manually refresh the page then it only shows on the correct post. I feel like every post's comment-box will need it's own unique ID to solve this, but I do not know how to do this with Laravel and make the JavaScript work.
also,
After I submit one comment I can no longer submit a second one because my "submit on enter/return key" functionally is no longer working. My cursor just moves to a new line, and I'm not able to post another comment.
Does anyone know a way to fix these problems?
EDIT
Here is my ajax so far
(function(){
$(document).on('submit', 'form[data-remote]', function(e){
e.preventDefault();
var form = $(this)
var target = form.closest('div.ajax-refresh');
var method = form.find('input[name="_method"]').val() || 'POST';
var url = form.prop('action');
$.ajax({
type: method,
url: url,
data: form.serialize(),
success: function(data) {
var tmp = $('<div>');
tmp.html(data);
target.html( tmp.find('.ajax-refresh').html() );
target.find('.type-box').html( tmp.find('.type-box').html() );
tmp.destroy();
}
});
});
})();
Please use the following to fix the issue:
$(document).on('submit', 'form[data-remote]', function(e){
e.preventDefault();
var form = $(this),
var target = form.closest('div.ajax-refresh');
var method = form.find('in......
......
.....
tmp.html(data);
target.html( tmp.find('.ajax-refresh').html() );
target.find('.type-box').html( tmp.find('.type-box').html() );
tmp.destroy();
}
});
});
The variable target will help you target just the right div to add the ajax response to.
Further, you would have to just reset the relevant form rather than replace the form markup. Otherwise each form will work only once.
UPDATE
The above code has been updated to use a delegated submit event -- $(document).on('submit', '.selector', ...) instead of $('.selector').on('submit', .....) since the form content is being replaced after each comment.
UPDATE 2
The following delegated keydown event should enable you to submit by pressing the enter key:
$(document).on('keydown', '.comments_create-form', function(e) {
if (e.keyCode == 13) {
e.preventDefault();
$(this).submit();
}
});
I am very confused as to how to write the code in javascript/jquery to allow for the proper submission of my form. I currently have a function to handle an on-click event, which loads the proper form. However, the form fails to submit successfully. I am using the .submit() function, but I think I am missing something here. I believe the URL I would have to place in that function would be the one being dynamically loaded in the onclick function. Is there a reasonable way to transfer this information? Or am I mistaken?
I have already confirmed that the correct URL is loaded and that the form behaves as intended if the URL is accessed manually. This issue seems to be unique to loading the URL
from select_my_book.html (the url to be loaded)
<form action="" method="POST" id="submitForm">
{% csrf_token %}
<!-- Code to render form elements -->
<input type="submit" value = "Buy" id="submit">
</form>
from get_my_book.html (the current url with the tabs/jumbotron)
{% for course in userprofile.courses.all%}
<div class="minicoursenav">
<a class='course_link' data='{{ course.pk }}' href='#' type="submit">
{{ course.name }}
</a>
</div>
{% endfor %}
from activate.js
function selectConditionURL(criterion, condition){
var url = "/books/select_my_book/"
return(url+ condition + "/" + criterion + "/")
}
function selectCourseURL(criterion){
var url = "/books/select_my_book/best_price/";
return (url + criterion + "/");
}
$('.course_link').click(function(e) {
e.preventDefault();
var course_pk = $(this).attr('data');
/*alert( course_pk );*/
var url = selectCourseURL(course_pk);
/*alert( url );*/
$("#best_price").load(url);
$("#very_good").load(selectConditionURL(course_pk, "very_good_condition"));
$("#good").load(selectConditionURL(course_pk, "good_condition"));
$("#fine").load(selectConditionURL(course_pk, "fine_condition"));
return false;
})
$(document).ready(function() {
$("#submitForm").submit(function(e) {
e.preventDefault();
$.ajax({
type: "POST",
/* NOT SURE WHAT SHOULD GO HERE */
success: function(response) {
console.log(response)
}
});
return false;
})
})
I have a template in Django with a foor loop that looks roughly like this:
{% if items %}
<form method="post" name="managerform" id="managerform" action="">{% csrf_token %}
{{ managerform }}
</form>
{% for item in items %}
<script type='text/javascript'>
var yes = function yes() { manager(function(response) {
if(response && response.status == 'user') {
var object = '{{ item }}'
document.managerform.item.value = object;
document.managerform.preferences.value = "Yes";
document.managerform.submit();
}
else{
authUser(); } });}
</script>
...
<button onclick=yes()>Yes</button>
...
{% endfor %}
Which submits the form, the problem is it always submits the last item from items. I've tried making yes take an argument, aitem, which didn't help because using <button onclick=yes("{{item}}")> failed entirely and doing:
<script>
aitem="{{ item }}"
</script>
<button onclick=yes(aitem)>
just uses the last item from items again.
Is there an obvious solution to this to anyone?
Change your button's HTML to be:
<button onclick='yes("{{item}}");'>Text</button>
And take out the <script> code completely outside of your django conditionals and loops so that it is always available, but change it to be:
var yes = function (item) {
manager(function (response) {
if (response && response.status == 'user') {
var object = item;
document.managerform.item.value = object; // You could combine this and the previous line
document.managerform.preferences.value = "Yes";
document.managerform.submit();
} else {
authUser();
}
});
}
This way, the only thing inside of your django for loop is the button, and each one ends up having a different argument for the yes function. The yes function now accepts a parameter and uses it like you wanted. You'll probably have to modify this to fit your needs though, because your code snippet seems like an example and is not exactly what you have.