I need to update an html table cell, which is created using a Django template for loop, once the selection of the pickerselect has changed. My HTML for the table:
<tbody>
{% for task in tasks %}
<tr>
<td>{{ task.taskNumber }}</td>
<td>{{ task.taskDescription }}</td>
<td><div name="dataOutput">{{task.thisPeriod}}</div></td>
</tr>
{% endfor %}
</tbody>
My HTML for the pickerselect:
<select name="selValue" class="selectpicker">
<option value="1">Current Period ({{currentPeriod}})</option>
<option value="2">Previous Period ({{previousPeriod}})</option>
</select>
My javascript:
<script language="JavaScript">
$(function() {
$('.selectpicker').on('change', function(){
var selected = $(this).find("option:selected").val();
var updateArray = document.getElementsByName("dataOutput");
for (var i = 0; 1 < updateArray.length; i++) {
if (selected == 1) {
updateArray[i].innerHTML = "{{task.thisPeriod}}";
} else if (selected == 2) {
updateArray[i].innerHTML = "temp";
}
}
});
});
</script>
At this point, the table row values update with the placeholder "temp" when selection 2 is made but when I change back to selection 1, no values appear in the table. I assume that this is because the Django template for loop is not being refreshed so it doesn't know how to render for the specific task in the loop {{task.thisPeriod}}. I have looked through a number of questions about refreshing div's using javascript but nothing has been effective... I always get the same result of blank values in the table.
It's kind of obvious, but you can't do it just like that, because javascript doesn't record the tasks django context that you passed to the template. You should use ajax to pass the selected value to a separate method in views.py, in there you find out the data and return it to the javascript, then render the div with the new data.
Related
In a django project, templates are used on the backend which render html that is displayed on the page. But when dealing with Channels (websockets) which receive 'notifications' from the server, I find that I have to re-write code in javascript to render the same html elements.
As an example, I started out with a HTML table element that in a django template page:
{% for t in tickets %}
<tr class="ticket-row">
<td>{{ t.id }}</td>
<td>
<a href="/tickets/{{ t.id }}">
{{ t.subject }}
</a>
</td>
<td>
<a href="/users/{{ t.user.id }}">
{{ t.user.name}}
</a>
</td>
<td class="tooltipped" data-tooltip="{{ t.date_time_updated }}">
{{ t.date_time_updated|naturaltime }}
</td>
<td class="tooltipped" data-tooltip="{{ t.date_time_created }}">
{{ t.date_time_created|naturaltime }}
</td>
....
When channels receives a notification in javascript, I want to insert another row to the table without refresh, so I write JS code to do so like this:
function insertTicketRow(ticket) {
document.getElementById("results").innerHTML = "Server: " + data.subject;
var table = document.getElementById('ticket_table');
var row = table.insertRow(1);
// Insert new cells (<td> elements) at the 1st and 2nd position of the "new" <tr> element:
var cellId = row.insertCell(0);
var cellSubject = row.insertCell(1);
var cellFrom = row.insertCell(2);
cellId.innerHTML = data.id;
cellSubject.innerHTML = linkElement("/tickets/" + data.id, data.subject);
cellFrom.innerHTML = linkElement("/users/" + data.user, data.author_name);
cellLastUpdated.innerHTML = data.date_time_updated;
cellCreatedOn.innerHTML = data.date_time_created;
....
}
Obviously this is not maintainable and I suspect the worst approach possible. What if I have 30 columns and not just a few? What about the attributes? Adding all this via javascript is doing the same work twice, AND, I have to put in extra logic to make the rendered HTML in javascript look the same like django's.
The question is, is there some method I can use to not write the same thing twice, but still be able to work with websockets and add elements to the page dynamically?
I have a Django application and a page where data is written to a table that I am styling using DataTables. I have a very simple problem that has proven remarkably complicated to figure out. I have a dropdown filter where users can select an option, click filter, and then an ajax request updates the html of the table without reloading the page. Problem is, this does not update the DataTable.
My html:
<table class="table" id="results-table">
<thead>
<tr>
<th scope="col">COL 1</th>
<th scope="col">COL 2</th>
<th scope="col">COL 3</th>
<th scope="col">COL 4</th>
<th scope="col">COL 5</th>
</tr>
</thead>
<tbody class="table_body">
{% include 'results/results_table.html' %}
</tbody>
</table>
results_table.html:
{% for result in result_set %}
<tr class="result-row">
<td>{{ result.col1 }}</td>
<td>{{ result.col2 }}</td>
<td>{{ result.col3 }}</td>
<td>{{ result.col4 }}</td>
<td>{{ result.col5 }}</td>
</tr>
{% endfor %}
javascript:
function filter_results() {
var IDs = [];
var IDSet = $('.id-select');
for (var i = 0; i < IDSet.length; i++) {
var ID = getID($(IDSet[i]));
IDs.push(ID);
}
// var data = [];
// data = $.ajax({
// url:"filter_results/" + IDs + "/",
// dataType: "json",
// async: false,
// cache: false,
// data: {},
// success: function(response) {
// $('#results-table').html(response);
// // console.log(response.length);
// // console.log(typeof response);
// //
// }
// }).responseJSON;
var dataTable = $('#results-table').DataTable();
dataTable.clear();
$('.table_body').html('').load("filter_results/" + IDs + "/", function() {
alert("Done");
});
dataTable.draw();
}
And views:
def filter_results(request, ids):
ids = [int(id) for id in ids.split(',')]
account_set = Account.objects.filter(id__in=ids)
form = ResultsFilterForm()
result_set = Result.objects.filter(account__in=account_set)
context = {
'form': form,
'result_set': result_set
}
return render(request, 'results/results_table.html', context)
What is happening is that the Ajax is correctly updating what I see on the HTML page, but it is not updating the actual data table. So I can filter for a particular ID, for instance, which has 2 results and this will work and show me the two results on the HTML page without reloading it. However, the DataTable still contains the rest of the results so there is still like a "next" page which makes no sense when there are only 2 results.
I also tried changing the view to return a JSON response with the code that is commented out of the JS and when I did that I got "Warning: DataTable encountered unexpected parameter '0' at row 0 column 0" even though the data coming from Django was the correct data in JSON form.
Really stuck here, appreciate the help.
I figured out a way to make it work. Though maybe not the "best" way to do this, it is simple so I hope this helps someone else. Change JavaScript as follows:
function filter_results() {
$('#results-table').DataTable().destroy();
var IDs = [];
var IDSet = $('.id-select');
for (var i = 0; i < IDSet.length; i++) {
var ID = getID($(IDSet[i]));
IDs.push(ID);
}
$('.table_body').html('').load("filter_results/" + IDs + "/", function() {
$('#results-table').DataTable();
});
}
That was it. All I needed to do was destroy the old DataTable at the beginning of the filter_results function and then re-create it. Note however that the recreation is the function to execute after the call to .load(). If you don't write it like this you will have an issue with JS recreating the DataTable before the the html is finished loading, which is a problem I encountered. Nesting that function inside the .load() call is an easy workaround though.
I know there is probably a better way to do this, one that involves updating rather than destroying and recreating the DataTable. If somebody knows of it feel free to post it as an answer but for now this workaround is good for me!
In my django app I have two models i.e Player and Team which are connected by many to many relationship. To add the data dynamically in my tables I want to use javascript to add Add row or Remove Row button in my forms but unable to do so.
Here are the details:
Models.py
class Player(models.Model):
pname = models.CharField(max_length=50)
hscore = models.IntegerField()
age = models.IntegerField()
def __str__(self):
return self.pname
class Team(models.Model):
tname = models.CharField(max_length=100)
player= models.ManyToManyField(Player)
def __str__(self):
return self.tname
Forms.py
class PlayerForm(forms.Form):
pname = forms.CharField()
hscore= forms.IntegerField()
age = forms.IntegerField()
PlayerFormset= formset_factory(PlayerForm)
class TeamForm(forms.Form):
tname= forms.CharField()
player= PlayerFormset()
Views.py
def post(request):
if request.POST:
form = TeamForm(request.POST)
form.player_instances = PlayerFormset(request.POST)
if form.is_valid():
team= Team()
team.tname= form.cleaned_data['tname']
team.save()
if form.player_instances.cleaned_data is not None:
for item in form.player_instances.cleaned_data:
player = Player()
player.pname= item['pname']
player.hscore= item['hscore']
player.age= item['age']
player.save()
team.player.add(player)
team.save()
else:
form = TeamForm()
return render(request, 'new.html', {'form':form})
new.html
<html>
<head>
<title>gffdfdf</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="/static/jquery.formset.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<form id="myForm" action="" method="post" class="">
{% csrf_token %}
<h2> Team</h2>
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }} : {{ field }}
{% endfor %}
{{ form.player.management_form }}
<h3> Product Instance(s)</h3>
<table id="table-product" class="table">
<thead>
<tr>
<th>player name</th>
<th>highest score</th>
<th>age</th>
</tr>
</thead>
{% for player in form.player %}
<tbody class="player-instances">
<tr>
<td>{{ player.pname }}</td>
<td>{{ player.hscore }}</td>
<td>{{ player.age }}</td>
</tr>
</tbody>
{% endfor %}
</table>
<button type="submit" class="btn btn-primary">save</button>
</form>
</div>
<script>
$(function () {
$('#myForm tbody tr').formset();
})
</script>
</body>
</html>
How can I use the javascript to add or delete the rows connected by many to many relationship ?
The above code gives us the following:
To keep it simple and generic, I reduced the OP's example to a single model and a basic formset, without the Team-Player many-to-many relation. The principle of the JavaScript part remains the same. If you do want to implement the many-to-many relation, you could use e.g. an inline formset, as explained here.
So, suppose we have a simple model:
class Player(models.Model):
name = models.CharField(max_length=50)
age = models.IntegerField()
Our view could look like this (based on the example in the docs):
def my_formset_view(request):
response = None
formset_class = modelformset_factory(
model=Player, fields=('name', 'age'), extra=0, can_delete=True)
if request.method == 'POST':
formset = formset_class(data=request.POST)
if formset.is_valid():
formset.save()
response = redirect(to='my_success_view')
else:
formset = formset_class()
if response is None:
response = render(
request, 'myapp/my_formset_template.html', dict(formset=formset))
return response
The my_formset_template.html django template below (skipping the boilerplate) enables us to add and remove formset-forms:
...
<template id="id_formset_empty_form">{{ formset.empty_form }}</template>
<form method="post" id="id_html_form" autocomplete="off">
{% csrf_token %}
<table id="id_formset_container">
{{ formset }}
</table>
<div id="id_formset_add_button" style="text-decoration: underline; cursor: pointer;">Add</div>
<input id="id_formset_submit_button" type="submit" value="Submit">
</form>
...
The HTML <template> element makes it easy to copy the content from formset.empty_form.
Side note: If we don't set autocomplete="off", the browser will cache the TOTAL_FORMS value on the management form, even after reloading the page.
Now, the following JavaScript does the job for me (no attempt was made to optimize, I just tried to make it easy to read):
window.addEventListener('load', (event) => {
// get form template and total number of forms from management form
const templateForm = document.getElementById('id_formset_empty_form');
const inputTotalForms = document.querySelector('input[id$="-TOTAL_FORMS"]');
const inputInitialForms = document.querySelector('input[id$="-INITIAL_FORMS"]');
// get our container (e.g. <table>, <ul>, or <div>) and "Add" button
const containerFormSet = document.getElementById('id_formset_container');
const buttonAdd = document.getElementById('id_formset_add_button');
const buttonSubmit = document.getElementById('id_formset_submit_button');
// event handlers
buttonAdd.onclick = addForm;
buttonSubmit.onclick = updateNameAttributes;
// form counters (note: proper form index bookkeeping is necessary
// because django's formset will create empty forms for any missing
// indices, and will discard forms with indices >= TOTAL_FORMS, which can
// lead to funny behavior in some edge cases)
const initialForms = Number(inputInitialForms.value);
let extraFormIndices = [];
let nextFormIndex = initialForms;
function addForm () {
// create DocumentFragment from template
const formFragment = templateForm.content.cloneNode(true);
// a django form is rendered as_table (default), as_ul, or as_p, so
// the fragment will contain one or more <tr>, <li>, or <p> elements,
// respectively.
for (let element of formFragment.children) {
// replace the __prefix__ placeholders from the empty form by the
// actual form index
element.innerHTML = element.innerHTML.replace(
/(?<=\w+-)(__prefix__|\d+)(?=-\w+)/g,
nextFormIndex.toString());
// add a custom attribute to simplify bookkeeping
element.dataset.formIndex = nextFormIndex.toString();
// add a delete click handler (if formset can_delete)
setDeleteHandler(element);
}
// move the fragment's children onto the DOM
// (the fragment is empty afterwards)
containerFormSet.appendChild(formFragment);
// keep track of form indices
extraFormIndices.push(nextFormIndex++);
}
function removeForm (event) {
// remove all elements with form-index matching that of the delete-input
const formIndex = event.target.dataset.formIndex;
for (let element of getFormElements(formIndex)) {
element.remove();
}
// remove form index from array
let indexIndex = extraFormIndices.indexOf(Number(formIndex));
if (indexIndex > -1) {
extraFormIndices.splice(indexIndex, 1);
}
}
function setDeleteHandler (containerElement) {
// modify DELETE checkbox in containerElement, if the checkbox exists
// (these checboxes are added by formset if can_delete)
const inputDelete = containerElement.querySelector('input[id$="-DELETE"]');
if (inputDelete) {
// duplicate the form index instead of relying on parentElement (more robust)
inputDelete.dataset.formIndex = containerElement.dataset.formIndex;
inputDelete.onclick = removeForm;
}
}
function getFormElements(index) {
// the data-form-index attribute is available as dataset.formIndex
// https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes#javascript_access
return containerFormSet.querySelectorAll('[data-form-index="' + index + '"]');
}
function updateNameAttributes (event) {
// make sure the name indices are consecutive and smaller than
// TOTAL_FORMS (the name attributes end up as dict keys on the server)
// note we do not need to update the indices in the id attributes etc.
for (let [consecutiveIndex, formIndex] of extraFormIndices.entries()) {
for (let formElement of getFormElements(formIndex)){
for (let element of formElement.querySelectorAll('input, select')) {
if ('name' in element) {
element.name = element.name.replace(
/(?<=\w+-)(__prefix__|\d+)(?=-\w+)/g,
(initialForms + consecutiveIndex).toString());
}
}
}
}
updateTotalFormCount();
}
function updateTotalFormCount (event) {
// note we could simply do initialForms + extraFormIndices.length
// to get the total form count, but that does not work if we have
// validation errors on forms that were added dynamically
const firstElement = templateForm.content.querySelector('input, select');
// select the first input or select element, then count how many ids
// with the same suffix occur in the formset container
if (firstElement) {
let suffix = firstElement.id.split('__prefix__')[1];
let selector = firstElement.tagName.toLowerCase() + '[id$="' + suffix + '"]';
let allElementsForId = containerFormSet.querySelectorAll(selector);
// update total form count
inputTotalForms.value = allElementsForId.length;
}
}
}, false);
Note that simply adding and removing formset forms is not that complicated, until something goes wrong: Approximately half the lines above have to do with handling edge cases, such as failed validation on forms that were added dynamically.
I am trying to update quantity number of individual items in a table whenever the user clicks the item in another table.
For example, I have list of all items in Table A
<tr ng-repeat="item in items">
<td>{{item.fid}}</td>
<td{{ item.fname }}</td>
<td>{{ item.calorie }}</td>
<td>{{ item.protein }}</td>
<td>{{ item.carb }}</td>
<td>{{ item.fat }}</td>
<td><button ng-click=additem(values)>Add</button>
<tr>
Now when the user clicks this Add button, the selected item gets added to another table (Table B).
I have disabled duplicates in Table B and want that if the user is repeatedly adding same item then the quantity of the item in Table B should increase.
Table B
<tr ng-repeat="sitem in sitems>
<td>{{sitem.fname}}</td>
<td>{{sitem.calorie}}</td>
<td>{{sitem.protein}}</td>
<td>{{sitem.carb}}</td>
<td>{{sitem.fat}}</td>
<td>*</td>
<td><button ng-click="removeItem(values)">Remove</button></td>
</tr>
is the one where i want the increased quantity to be shown.
I have tried using "this" keyword but didn't worked, I am new to angular so i don't know what all are my options and got mixed up.
You have to keep track of two separate array to accomplish this.
This is the code snippet: `
$scope.additem = function(item) {
var index = $scope.sitems.indexOf(item);
if (index === -1) {
item.quantity = 1;
$scope.sitems.push(item);
return;
}
item.quantity++;
$scope.sitems[index] = item;
};
`
Complete Working fiddle: https://jsfiddle.net/nbakliwal18/4kbo7Lfo/2/
Also you check for quantity before adding.
You can simply pass item to the function addItem() :
<td><button ng-click=addItem(item)>Add</button>
and push this item to your array sitems in your function :
$scope.addItem = function(item) {
$scope.sitems.push(item);
}
A workaround for the duplicates in ng-repeat is to add track by $index to it :
<tr ng-repeat="sitem in sitems track by $index">
Update 1:
Updated my answer with working example in Plunker
Update 2:
Here is an example with lodash for quantity Plunker
My problems are:
When I click the print button, it shows the data by text not in table.
I want to print data table depends on the current table show. means
here is on the current page has list of all data and select option.
When I select or filter table it still print all the data. The data
here is collected from database.
After logged in, why does the image for next page not show?
Here is my JavaScript:
<script lang='javascript'>
$(document).ready(function(){
$('#printPage').click(function(){
var data = '<input type="button" value="Print this page" onClick="window.print()">';
data += '<div id="div_print">';
data += $('#report').html();
data += '</div>';
myWindow=window.open('','','width=200,height=100');
myWindow.innerWidth = screen.width;
myWindow.innerHeight = screen.height;
myWindow.screenX = 0;
myWindow.screenY = 0;
myWindow.document.write(data);
myWindow.focus();
});
});
</script>
Blade template:
<tbody id="result">
#foreach($profiles as $profile)
<tr>
<td class="student_id" width="15%">{{$profile->student_id }}</td>
<td class="name" width="30%">{{$profile->student_name }}</td>
<td class="program" width="30%"> {{$profile->program }}</td>
<td class="faculty" width="25%">{{$profile->faculty }} </td>
</tr>
#endforeach
</tbody>
</table>
<p align="center"><button id="printPage">Print</button></p>
Rather than doing the method I suggest that you use a PDF generation plugin such as pdf-laravel.
PDF-LARAVEL :
to output to a file
$router->get('/pdf/output', function() {
$html = view('pdfs.example')->render();
PDF::load($html)
->filename('/tmp/example1.pdf')
->output();
return 'PDF saved';
});
Adding the functionality to your controller
class HomeController extends Controller
{
private $pdf;
public function __construct(Pdf $pdf)
{
$this->pdf = $pdf;
}
public function helloWorld()
{
$html = view('pdfs.example1')->render();
return $this->pdf
->load($html)
->show();
}
If you want to search for other options then go to packagist.org and search for "pdf laravel" and you will find some packages built for Laravel 5 PDF generation.
Answer to the question 2:
I want to print data table depends on the current table show. means
here is on the current page has list of all data and select option.
When I select or filter table it still print all the data. The data
here is collected from database
If you want to filter data and display there, you have two options, either filter data form the controller or you can use jQuery Datatables for that. Link here
All you have to do is:
Add the Js.
https://cdn.datatables.net/1.10.10/css/jquery.dataTables.min.css
Add the css
http://cdn.datatables.net/1.10.10/js/jquery.dataTables.min.js
Call your table.
$(document).ready(function(){
$('#myTable').DataTable();
});
function printData()
{
var print_ = document.getElementById("table_name");
win = window.open("");
win.document.write(print_.outerHTML);
win.print();
win.close();
}
The above function will print the data of a table which are on the current page due to the window.open("") function.