Latency when loading html content with FOSJsRoutingBundle and Ajax (route parameter {id}) - javascript

I am using FOSjSrouting in my symfony2.7 project.
I have this html.twig view code:
<table>
<!--table header code ...etc... -->
<tbody>
{% for currentData in arrayData %}
<tr>
<td>{{ currentData.first_name}}</td>
<td>{{ currentData.last_name}}</td>
<td>{{ currentData.order}}</td>
<td>{{ currentData.category}}</td>
<td>{{ currentData.location}}</td>
<td>{{ currentData.price}}</td>
<td>
<a><button class="btn btn-info btn-xs showDetail" href="{{ path('detailsData', {'idData': currentData.idData }) }}">Show Detail</button></a> <!-- to open dialog tag through Ajax for each lines -->
<button class="btn btn-warning btn-xs">Edit</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- the dialog window to load detail content -->
<dialog id="window"></dialog>
<!-- javascript to open and close dialog window -->
<script>
(function() {
var dialog = document.getElementById('window');
var showButtons = document.getElementsByClassName('showDetail');
var showDialog = function() {
console.log(this);
dialog.show();
dialog.load(Routing.generate('detailsData', {'idData': currentData.idData }) }})
};
var i, len = showButtons.length;
for(i = 0; i < len; ++i) {
showButtons[i].onclick = showDialog;
}
document.getElementById('exit').onclick = function() {
dialog.close();
};
})();
</script>
As you can see, foreach data I want to display, I have a detail button. This detail button refers to another page which displays linked data to the current data I clicked on.
This the route for the detail view (respecting the FOSjSrouting needs):
detailData:
pattern: /manage/order/detail/{idData}
defaults: { _controller: MySpaceMyBundle:MyController:detailData }
requirements:
methods: GET
options:
expose: true
The purpose is to load via Ajax, jQuery, FOSjSrouting the linked datas in the dialog tag with the id="window" to the currentData.idData.
But when I click on the detail button, I have this error (in my browser console):
Uncaught ReferenceError: currentData is not defined
I understand that I need to take care about the {id} parameter of my route.
How can I load the page that the detail button refers to and displays the right linked data through ajax with my current script (at the end of my html code)?
EDIT
I add this code in javascript at the end of my script tag in my html.twig view, it allows me to display the datas I want in my dialog tag:
$('.showDetail').click(function() {
$.ajax({
'url': Routing.generate('detailsData', {'idData': $(this).val()}),
'cache' : false,
'success': function(loadDetail) {
$('#window').html(loadDetail);
}
});
});
The problem occured is that I have a latency to display the dialog tag, and just before displaying the current data I want, the last one I clicked on are displaying before for a few second, and then my current data I want is display later.
Maybe It's a cache problem?
Moreover, the first time I click on the detailButton, the dialog is displaying empty, then after (latency), the data are displaying.
How can I fix these problems?

The problem is, that the variable is not defined in javascript. You have to insert curly brackets to let twig translate the twig variable into the corresponding value like this:
{'idData': {{currentData.idData}} }
Unfortunately therefor it has to be in the for loop as well - so it might be better to safe the value in an data attribute and read it by using javascript.
You could also use the already populated href attribute like this (using jQuery as you said you are doing):
dialog.load($(this).attr('href'))

Related

JavaScript Uncaught TypeError: Cannot read property 'style' of null: Button Onclick makes html disappear

In a django project, I've set up a JavaScript function to select/block certain pages. I have another JavaScript function for the pages that are being loaded. This function has an onclick event listener that submits text to a form field. The problem I'm having is that when I click a button to add text to a form field, the entire page disappears. Both functions are in a static file called "main.js" The exact error message showing in the console is...
Uncaught TypeError: Cannot read property 'style' of null
at showPage (players:6382)
at HTMLButtonElement.button.onclick (players:6388)
Here's the function controlling showing/blocking of individual pages.
function showPage(page) {
document.querySelectorAll('tbody').forEach(tbody => {tbody.style.display = 'none';})
document.querySelector(`#${page}`).style.display = 'table-row-group'; ---(This line in error message)
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('button').forEach(button => {
button.onclick = function() {
showPage(this.dataset.page); ---(This line in error message)
}
});
});
Here's an example from the html template of a button targeted by this function.
<button class="posbutton" data-page="page1">QB</button>
This is the function that submits text to a form field.
function myFunction(txt) {
var myTxt = txt;
if (txt.includes('QB')) {
document.getElementById("QB_name").value = myTxt;
}
else if (txt.includes('RB')) {
document.getElementById("RB_name").value = myTxt;
}
else if (txt.includes('WR')) {
document.getElementById("WR_name").value = myTxt;
}
else if (txt.includes('TE')) {
document.getElementById("TE_name").value = myTxt;
}
else if (txt.includes('K')) {
document.getElementById("K_name").value = myTxt;
}
}
Here's an example of the html element targetted by the showPage function. It also contains a line with the button that's linked to the myFunction for submitting text.
<tbody id="page1">
{% for q in QBpage %}
<tr>
<th scope="row">
</th>
<td><h6>{{ q.player_name }}</h6></td>
<td><input type="button" class="btn btn-outline-primary btn-sm m-0 waves-effect" onclick="myFunction('{{ player_data.player_name }} {{ player_data.position }}')">Add</input></td>
<td><h6> {{ q.team }} </h6></td>
<td><h6> {{ q.position }} </h6></td>
<td><h6>{{ q.points }}</h6></td>
</tr>
{% endfor %}
</tbody>
Before putting both JavaScript functions into a static file, I experimented with putting the script tags at the start or at the end of the in all manner of combinations. So far, the advice suggested here Why does jQuery or a DOM method such as getElementById not find the element? hasn't been helpful for me. I've tried a lot of the suggestions in the answers there but I'm still having the same problem.
The error
at HTMLButtonElement.button.onclick (main.js:15) still persists which is showPage(this.dataset.page);this line in the showPage function.
I'm really stuck on this. Any advice is greatly appreciated.
document.querySelector('#'+page).style.display = 'table-row-group';
use this

Django: Writing JavaScript inside template vs loading it

I am using JavaScript to add comment without refreshing the page. When I am using JavaScript inside the template it is working perfectly fine but if I am writing the JavaScript to a file and loading the file inside the template then it is not displaying the name of the person who wrote the comment (It is displaying the comment fine). Below is the JavaScript I used inside the template:
function create_comment(event,div_id,form_id,commentinsert_id) {
event.preventDefault();
var text_id = '#'.concat(div_id,' ','#comment-text')
var slug_id = '#'.concat(div_id,' ','#post_slug')
var appenddiv_id = '#'.concat(div_id)
comment_count ++;
var divcommentinsert_id = 'inserted_comment-'.concat(comment_count.toString())
$.ajax({
url : "create_comment/", // the endpoint
type : "POST", // http method
data : { text : $(text_id).val(), post_slug: $(slug_id).val(),},
// handle a successful response
success : function(json) {
div.innerHTML = `
<div id = `+divcommentinsert_id+` class="card-footer">
<div class="row">
<div>
<img src="{{user.profile.profile_picture.url}}" alt="" width="40" height="40">
</div>
<div class="col-md-8">
<b style="color:rgb(240, 0, 0);"> {{user.first_name}} {{user.last_name}}</b>
<br>
`+json.text+`
<a onClick="edit_comment('`+json.slug+`','`+divcommentinsert_id+`','`+json.text+`')"><i class="fa fa-edit"></i></a>
<a onClick="delete_comment('`+json.slug+`','`+divcommentinsert_id+`')"><i class="fa fa-trash-o"></i></a>
</div>
</div>
</div>`;
document.getElementById(commentinsert_id).appendChild(div);
document.getElementById(form_id).reset();
},
// handle a non-successful response
error : function(xhr,errmsg,err) {
$('#results').html("<div class='alert-box alert radius' data-alert>Oops! We have encountered an error: "+errmsg+
" <a href='#' class='close'>×</a></div>"); // add the error to the dom
console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console
}
});
};
In the above code <b style="color:rgb(240, 0, 0);"> {{user.first_name}} {{user.last_name}}</b> is used to display the name. It is displaying name of the person who added the comment but when I copy the same function to comments.js file and load the file like <script src="{% static 'scripts/comments.js' %}"></script> then its not displaying the name instead it is displaying {{user.first_name}} {{user.last_name}}. I am using this javascript in several pages, I thought it is best to write to file and load the file instead of writing the same script in every page. Can someone help me with this?
{{ dictionary.key }}, etc in a template is how you use variables with the django template engine.
A static javascript file is not a template. It isn't processed by the django template engine.
Put the values you need in your html (through the template engine), access them from JS.
Basic ideas:
Create a JS variable (eg: <script>var user_full_name = "{{ user.user_name }} {{ user.last_name }}"</script>), use user_full_name inside the ajax sucess callback.
Put the values inside some html element (eg: <input type="hidden" id="user_name" value="{{ user.user_name }}">) and get it with JS/jQuery $("#user_name").val().
edit: added missing closing double quote to the user_full_name declaration.

How to pass a variable to a helper and display its in the template?

I'd like to collect a variable(user _id)collected from a template, and pass it to another template using session. Then I want to display this variable.
Actually it seems to work in the collection of the variable and the pass to the other template, but I'm not able to display the user's info in the second template...
This is my code:
HTML
<template name="main"><!--Template 1-->
<table>
<tr>
<th>Patient</th>
</tr>
{{#each allUsers}}
<tr>
<th><label><input type="radio" class="selected" name="patient" value="{{this._id}}"><i class="fa fa-user"></i> {{this.profile.lastName}} {{this.profile.firstName}}</label></th>
</tr>
{{/each}}
</table>
{{>test}}
</template>
<template name="test"> <!--Template 2-->
<p>Name is <button class="test" name="patient" value="{{this._id}}">Test</button></p>
<div name="show">Name: {{this.profile.firstName}}</div>
</template>
JS
Template.main.events({
'click .selected': function (){
var selPat = $('input[name="patient"]:checked').val();
Session.set("selPat",selPat);
console.log("collect", selPat);
}
});
Template.test.events({
'click .test': function(){
var PAT= Meteor.users.findOne({ _id: Session.get("selPat")});
console.log("pass", PAT);
return PAT;
}
});
Template.patients.helpers({
allUsers: function() {
return Meteor.users.find({});
}
});
I want to display in the template 2 the first name of the user selected in the template 1 with {{this.profile.firstName}}
I believe this is what you are doing:
You are choosing patient's id from a list of patients via the radio buttons in the main template. [this implementation is correct]
You are setting the patient id in a session in the main template's events. [this implementation is correct]
When you click the "test" button in the test template, it should reveal the user's first name in the div below the button. [...not quite]
You are unable to display anything in <div name="show">Name: {{this.profile.firstName}}</div> because you don't have a relevant helper supplying the template with that information.
Although clicking a button to reveal the patient's firstName in the test template sounds a bit redundant, I'm sure you have some reason to do it in that manner.
I propose that you wrap the div inside an if block. The if condition renders true, when the button is clicked, and hence the div element is shown.
<template name="test"> <!--Template 2-->
<p>Name is <button class="test" name="patient" value="{{this._id}}">Test</button></p>
{{#if isButtonClicked}}
<div name="show">Name: {{data.profile.firstName}}</div>
{{/if}}
</template>
Your helpers and events will be like so:
Template.test.events({
'click .test': function(){
// set this session to true when the button has been clicked.
Session.set("testClicked", true);
}
});
Template.test.helpers({
isButtonClicked: function(){
// return the if block's validation.
return Session.get("testClicked");
},
data: function(){
// return the actual user data retrieved from the collection.
var PAT= Meteor.users.findOne({ _id: Session.get("selPat")});
console.log("pass", PAT);
return PAT;
});
Note:
You might want to make sure that the div does not stay open when you select a different patient from the list of radio buttons. Not doing so will make the div be visible when you first click the button, and remain open until you refresh the page, even when you select a different patient.
You could set testClicked to false or undefined in Template.main.events --> 'click .selected'

Django : combine variable and HTML id in template

I'm looking for a way to "concatene" a django object list (get via the get_queryset in the views.py) with a Javascript variable. then concatene them to a field of a model object.
My model is a blog post, and contains the post_title field (a charfield).
In my index.html, I have a pager button with id="1" or id="2" corresponding to the index of the page and a onClick="displayPostContentOnPageButtonCliked(this.id)" attribute.
My index.html retrieve all post objects in a context_object_name called all_post_by_date.
In my index.html, I want to display on alert dialog the post title corresponding to the button clicked, something like:
<script>
function displayPostContentOnPageButtonCliked(page_button_clicked_id){
var all_posts_by_date = "{{all_posts_by_date.page_button_clicked_id.post_title}}";
alert(all_posts_by_date);
}
</script>
But this doesn't work. I tried to add filter or create my own filter (called get_index, but only manage to get like: all_posts_by_date|get_index:page_button_clicked_id and not be able to combine the .post_title part.
My question is: how to get {{all_posts_by_date.page_button_clicked_id.post_title}} to work, and alert "Title of post 3" when the button 3 is clicked ?
Thank u !
Two solutions for you: (1) send in the data you want to your JS function in the call to the JS function.
<script>
function showPostTitle(post_title){
alert(post_title);
}
</script>
. . .
{% for x in all_posts_by_date %}
<div id='blog-post-{{ x.id }}' on-click="showPostTitle('{{ x.post_title }}')">
. . .
{% endfor %}
Or you can just serialize the data yourself for later use:
<script>
var titles = [];
{% for x in all_posts_by_date %}
titles.push('{{ x.post_title }}');
{% endfor %}
function displayPostContentOnPageButtonCliked(nId) {
var title = titles[nId - 1];
alert(title);
}
</script>

Partial view with ajax and jQuery UI.Dialog

I am using a standard MVC4 EF5 setup and have a standard view which loads data from the db onto a table.
At the start of the table I have a column for each record with an Add button. The functionality I want is to click the button, popup a model dialog box with a form and add something to the item in the grid that was clicked (a 1 to many).
Lets say I have a list of vans available shown in the list. And when I click the add button beside the particular van where I want to add a passenger, I want a popup to show that allows me to type the details of the passenger so they can be assigned to that van.
I think I am over complicating this. But my brain is fried. I tried partial views with ajax. I tried jQuery UI.Dialog. Im just lost. I am trying to figure out how to find the id of the record I clicked (given the buttons are all generated by a for each loop in the view as normal and numbering them 1 to X does not tell me the id of the record I clicked). So even if I get the popup showing, I wont know which van to assign the passenger to.
If your woundering where the passenger list is coming from, its another table. And effectively any passenger can be assigned to any van. Its hypothetical.
Im actually working on a document generator and so there is a many to many relationship between document parts and documents (a given document part, can appear or belong to many documents, and a document can contain many document parts). I know its messy, this is why I did not want to use the real example.
I'm thinking its maybe an easy enough problem to solve but I have been at it since Friday and the brain left home!
Edit: Adding Code:
Here is the main view: The main problem I am having with this is the way the grid is constructed. I think its partially razor, partially html, partially html helper, and partially javascript. I don't know which part is which, but I just need to get a popup to show for each button in the table, and to have an id I can assign values to. I cant figure out how to do it here.
Html.Grid(dbaccess().Where(c => something = something
).Select(o => new
{
Name = o.Name,
Comment = o.Comment,
Status = o.Status,
}
, "grdConfiguration", 0, htmlRowClass: (p) => (row++ % 2 != 0) ? "" : "oddRow"
, columns: new[]{
//THIS IS THE PROBLEM LINE BELOW .... It shows a button in the table, but...
//how do I make it unique. Is it even necessary to do so.
// How do I get the ID of the record at this location when this button is pressed.
//This is the code as originally posted: For context
new Helpers.GridColumn(value: (a) => "<input type=\"button\" class=\"btn\" id=\"BtnHello\" value=\"Add\" />"),
//for some reason when I try the following alternative as suggest by the answers so far - it doesn't work.
new Helpers.GridColumn(value: (a) => "<input type=\"button\" class=\"btn\" data-ThisId=\"#model.SomeId\" value=\"Add\" />"),
//THIS IS THE PROBLEM LINE ABOVE....
there is more columns but this button calls the jQuery...
On this view I also have some Div tags in which to load the partial... I can actually get this to popup. But that's about all I can do. And only when I click the first button in the table.
<div id='SomePopUp' style='display: none;'>
//#using (Html.BeginForm())
//{
// <div>
// <span class="display-label">Quantity: </span>
// <span class="display-field"><input type="text" id="txtQuantity" /></span>
// </div>
// <div>
// <span class="display-label">Comments: </span>
// <span class="display-field"><textarea rows="7"></textarea></span>
// </div>
//}
</div>
I also have a script section on this view with the code for the popup:
<script type="text/javascript">
$("#BtnHello").click(function ()
{
$("#SomePopUp").dialog(
{
resizable: false,
height: 400,
width: 400,
modal: true,
title:"add to {Some ID}:", //I want the id to show here so I know I have the record I want.
buttons:
{
Submit : function ()
{
$(this).dialog('Some Text');
},
Cancel: function ()
{
$(this).dialog('close');
}
}
});
});
</script>
I have a controller:
[HttpGet]
public ActionResult AddExtra(int id)
{
//Fairly sure I should be doing something with this id, but how do I get it from the button.
return PartialView();
}
And for the partial view I have
#model CM.ViewModels.AddExtraPackagesViewModel
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h3>Add Something</h3>
</div>
<div>
//I was using ajax here...
#*#using (Ajax.BeginForm("DoSomething", "Something", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "list-of-something"
}))
{
<div class="modal-body">
#Html.TextBoxFor(x => x.Quantity);
#Html.TextAreaFor(x => x.Comment);
</div>
<div class="modal-footer">
<button class="btn btn-success" id="submit">Save</button>
Close
</div>
}
</div>
I made a little view model too but...
public class AddExtraViewModel
{
public int Id { get; set; }
public string Quantity { get; set; }
public string Comment { get; set; }
}
I apologise if this is all over the place but I did not write the original code. There were about 7 other programmers here before me and I'm just struggling to get through it.
Any help would be appreciated.
I think you would want something like this (using jQuery and jQuery UI):
Controller:
public ActionResult SomeAction(int id) {
return View(new YourModel { Id = id });
}
Partial View:
#model YourProject.Models.YourModel
// Partial view content e.g. the form etc.
Your view:
/<!-- html etc. -->
<table>
<tr>
<td>Add</td>
</tr>
</table>
<script>
$(function(){
$(".add-button").click(function(){
var options = {
autoOpen: false
}
var dialog = $("<div>").dialog(options);
var id = $(this).data("theId");
dialog.load("the/url/to/the/controller/action", { id: id }, function(){
dialog.dialog("open");
dialog.find("form").submit(function(){
// do stuff
dialog.remove();
return false;
});
});
});
});
</script>
if you are building buttons in a forloop you don't want to define an id on the button. Duplicate id's on a view can cause lots of issues. Use a class on the buttons instead to trigger off of and use $(this) in your script to get details of the button that was clicked. To access buttons on a partial or on items that are added to your page after page load you need to tie the click event for that button to the document like this
$(document).on("click", ".btnDetails", function(){
//your script here
});
The other example uses "this" and shows how you can pass the id of the clicked button back to the controller. The controller will need to be a little different though
public PartialViewResult PopulatePartial(int ID){
var Model = //populate your model based on the passed id
return PartialView("PartialViewName", Model);
}

Categories

Resources