create table with dynamic rows and columns with django html template - javascript

I'm trying to create a table with dynamic rows and columns based on the results list with django html template. The number of records and header number could change. I am using two for loops to get the number of rows and the column number. I'm having a hard time trying to output the actual values in the table. The idea was to "index" from the second for loop and applying it to "each" of first loop. I know that the syntax is definitely wrong, but is something that django can do? If not, is there any suggestions that I can research on how to implement this? Thanks!!
list = [
{'header_a': foo1, 'header_b': foo2, 'header_c': foo3},
{'header_a': foo1, 'header_b': foo2, 'header_c': foo3},
{'header_a': foo3, 'header_b': foo3, 'header_c': foo3},
{'header_a': foo4, 'header_b': foo4, 'header_c': foo4},
]
Sample Table
header_a | header_b | header_c
foo1 | foo1 | foo1
foo2 | foo2 | foo2
foo3 | foo3 | foo3
foo4 | foo4 | foo4
or
list_2 = [
{'header_c': c_foo1, 'header_d': d_foo1},
{'header_c': c_foo2, 'header_d': d_foo2},
]
Sample Table 2
header_c | header_d
c_foo1 | d_foo1
c_foo2 | d_foo2
<table>
<thead>
<tr>
{% for index in list.0 %}
<th>{{ index }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for each in list %}
<tr>
{% for index in a %}
<td>{{ each.{{index}} }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>

Well, as you have a list of dicionaries in that schema, this is how you can iterate it inside the template:
<table>
<thead>
<tr>
{% for item in list %}
{% if forloop.first %}
{% for h in item %}
<th>{{ h }}</th>
{% endfor %}
{% endif%}
{% endfor %}
</tr>
</thead>
<tbody>
{% for item in list %}
<tr>
{% for key,value in item.items %}
<td>{{ value}}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
Assuming you have the same keys in every dictionary (column headers or fields as you name them), you first iterate only the first dicionary to get its keys, then, you iterate again the values for rows...
Not the most efficient solution tho, it would be great if you can rewrite the way you create that list.
Hope it helps and please confirm if it works

maybe you should check out django tables? It's a pretty known tool to creating powerful tables with django. You can create them based on a model, but you can also simply pass the data directly to them. I also once used it to create a dynamic table. It takes all the job from the template and add it to the view where things are easier to manipulate.

Related

Can I Reorder a LIst Based On User Provided Values Using Only HTMX?

I have HTMX working. The code below is fully functional. The one piece I'd like to incorporate, I can't figure out how to do it. The user is able to provide a number rank...but when they click save, the view returns with the item at the top. Only after they click reload does the list sort itself based on how I defined the attributes with the model.
Here's my HTML...
<h1 class="title62">Tasks</h1>
<button class="button36" hx-get="{% url 'MyTasks:create_task_form' %}" hx-target="#taskforms">Add Task</button>
<div id="taskforms"></div>
<div id="tblData">
{% if tasks %}
{% for task in tasks %}
{% include "partials/task_detail.html" %}
{% endfor %}
</div>
{% endif %}
<div hx-target="this" hx-swap="outerHTML" hx-headers='{"X-CSRFToken":"{{ csrf_token }}"}' class="" >
<form method="POST">
{% csrf_token %}
<div class="table22">
<table class="table23">
<thead>
<tr>
<th class="title67">Number</th>
<th class="title67">Task</th>
</tr>
</thead>
<tbody>
<button class="button35" hx-post=".">
Save
</button>
<button type="button" class="button33">
Delete
</button>
<tr>
<td class="title73">{{ form.number }}</td>
<td class="title73">{{ form.task }}</td>
</tr>
</tbody>
</table>
</div>
</form>
</div>
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
})
</script>
<div hx-target="this" class="">
<div class="table22">
<table class="table23">
<thead>
<tr>
<th class="title67">Number</th>
<th class="title67">Task</th>
</tr>
</thead>
<tbody>
<button class="button35" hx-get="{% url 'MyTasks:update_task' task.id %}" hx-swap="outerHTML">
Update
</button>
<button class="button34" hx-confirm="Are you sure you want to delete?" hx-post="{% url 'MyTasks:delete_task' task.id %}" hx-swap="outerHTML">
Delete
</button>
<tr>
<td class="title70">{{ task.number }}</td>
<td class="title70">{{ task.task }}</td>
</tr>
</tbody>
</table>
</div>
</div>
My Model...
class Task(models.Model):
task = models.TextField(max_length=264,blank=True,null=True,unique=False)
number = models.PositiveIntegerField(default=1)
class Meta:
ordering = ["number"]
def __str__(self):
return self.task
I have begun to explore using Javascript to do HTML sorting to approach my issue that way instead. It just seems to me as capable as HTMX is there should be a way for me to do it leveraging HTMX. Thanks in advance for any thoughts.
I think the easiest way would be just to swap in the entire list on save instead of just the new item - that way you can take advantage of the ordering in the model.
EDIT:
In order to swap a list of the task in instead of the saved task, you will need to modify your view to return a list of the tasks instead of just the task being saved - like this for a CreateView:
class TaskCreateView(CreateView):
model = Task
form_class = TaskForm
template_name = 'task/_create_form.html'
def form_valid(self, form):
self.object = form.save()
tasks = Task.objects.all()
return render(self.request, 'task/_task_list.html', {'tasks': tasks})
You would also need to make partial template that would render all the tasks - something like this:
<div id="tblData">
{% if tasks %}
<ul>
{% for task in tasks %}
<li>{{ task.order }} - {{ task.task }}</li>
{% endfor %}
</ul>
{% endif %}
The HTMX technique you are looking for is the Out of Band Swapping which is basically 1 request with multiple targets. The targets can be anywhere on the page. In your case: when the user creates, changes or deletes a task, the response should also contain the updated list of tasks. It requires only a few modifications. For example a task updating view:
def update_view(request):
tasks_updated = False
tasks = None
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
# Update the task in the database
...
task.save()
tasks_updated = True
else:
# Return the form with errors
return render(request, 'task_form.html', {'form': form})
if tasks_updated:
# Fetch new list of tasks
tasks = Task.objects.order_by('number')
return render(request, 'task.html', {'tasks': tasks, 'task': task})
We have the tasks_updated tracking variable that we switch to True if we updated a task in the database, so the task list needs an update on the frontend. Just before we render the template, we check the value of this variable and fetch the tasks if needed.
And the partial template:
<div>
<!-- Render the task's table as usual. -->
</div>
{% if tasks %}
<div id="tblData" hx-swap-oob="true">
{% for task in tasks %}
{% include "partials/task_detail.html" %}
{% endfor %}
</div>
{% endif %}
Here we render the table only we have the tasks variable, so at least one of the task was updated therefore we loaded the tasks as well. The hx-swap-oob="true" tells HTMX to swap the element having tblData id.
Basically that's it. Just include an OOB-Swap task list in each response, where the task list needs an update. If you load the "new task form" you don't need it, but if you add/update/delete a task, you need a fresh OOB-Swap task list in the response (fetched from the database after the task operation has been finished).

search box for all the pages after Django pagination of table

I have developed a web app using Django.
I have created a table pagination.
how could I create a search input for all the data in all the pages of table?
view.py
def Browse_and_adopt(request):
Title_list = Title.objects.all()
page = request.GET.get('page', 1)
paginator = Paginator(Title_list, 10)
try:
Titles = paginator.page(page)
except PageNotAnInteger:
Titles = paginator.page(1)
except EmptyPage:
Titles = paginator.page(paginator.num_pages)
page_obj = paginator.get_page(page)
return render(request, 'bms/inbox/Browse_and_adopt.html', {'Titles': Titles, 'page_obj': page_obj})
Browse_and_adopt.html
<table id="myTable">
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody>
% for book in page_obj %}
<tr>
<td>{{ book.title }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
next
last »
{% endif %}
</span>
</div>
Now only the first page shows, how to create a search bar for all the data in the table?
The view does not know whick object you are looking for.
There is two ways to do so.
In the frontend end where you write a JS callback event listener that filter all the supplied table data. " not optimal ''
Or you can create a custom view that recieve the object pk via post request and return the filtered result e.g url :
urlpatterns = [
path('update/<int:pk>/', updateViewSet.as_view()),
]

Create an iterative counter in DJango template

I've checked a lot of other questions and I haven't seen my particular scenario really addressed and I've tried a lot of things out without success.
What I have is a DJango for loop in my HTML code, and within the for loop is an if statement checking if each element from the list that is being looped through equals a certain value. If that is true, then an entry is created on the page. I need to dynamically print the element number (eg. entry 1 would display as 1. and entry 2 would display as 2.)
The two best attempts I have made are:
1.
<!-- this approach prints out 1 for each entry -->
{% with counter=0 %}
{% for q in questionnaire.questions %}
{% if q.answer %}
<div class="row"><h3>
{{ counter|add:1 }}. {{ q.name }}
</h3></div>
<!-- some other code-->
{% endif %}
{% endfor %}
{% endwith %}
{% for q in questionnaire.questions %}
{% if q.answer %}
<div class="row"><h3>
<span id="displayCount">0</span>. {{ q.name }}
</h3></div>
<!-- some other code-->
{% endif %}
{% endfor %}
<script type="text/javascript">
var count = 0;
var display = document.getElementById("displayCount");
count++;
display.innerHTML = count;
</script>
Any help would be appreciated
You can access the built-in counter of your for loop using forloop.counter. It starts at 1, you can also you forloop.counter0 if you'd like to start at zero.
{% for q in questionnaire.questions %}
{% if q.answer %}
<div class="row">
<h3>
{{ forloop.counter }}. {{ q.name }}
</h3>
</div>
<!-- some other code-->
{% endif %}
{% endfor %}
Filter your queryset in your view as to avoid issues with indexing and separating presentation from logic.

Dynamically add or remove table rows with jQuery or Angular?

This is the code I have. I'm using Symfony/Twig to pass the variables and translation strings in (if anyone was unsure what the {{, }}, {% trans %} etc was for).
Please see the line where I have the glyphicon glyphicon-camera - what I want is for the user to be able to click this, and a new row appears directly below containing the contents of row.getPhoto() - the icon will only appear if row.getPhoto() is not null, so therefore clicking it will always mean there is content to show.
Likewise, clicking the photo icon again will make the row disappear.
How can I do this? I'm not sure if I should use jQuery or Angular (I am using both in other places in the project, so both are easily available for me). Any comments welcome, thank you.
<table class="table">
<tr>
<th width="10%">{% trans %} header.item {% endtrans %}</th>
<th width="60%">{% trans %} header.action {% endtrans %}</th>
<th width="10%">{% trans %} header.option1 {% endtrans %}</th>
<th width="10%">{% trans %} header.option2 {% endtrans %}</th>
<th width="10%">{% trans %} header.option3 {% endtrans %}</th>
</tr>
{% for row in showRows(allItems) %}
<tr>
<td>
{{ row.getItem() }}
</td>
<td>
{{ row.getAction() }} {% if row.getPhoto() is not null %} <span class="pull-right show-hide-photo glyphicon glyphicon-camera"></span>{% endif %}
</td>
<td>
{% if row.getOption1() %}<span class="glyphicon glyphicon-ok"></span>{% endif %}
</td>
<td>
{% if row.getOption2() %}<span class="glyphicon glyphicon-ok"></span>{% endif %}
</td>
<td>
{% if row.getOption3() %}<span class="glyphicon glyphicon-ok"></span>{% endif %}
</td>
</tr>
{% endfor %}
</table>
Only jQuery I have right now is this, to make the icon appear like a link when hovered over:
// Photo button
$('.show-hide-photo').css('cursor', 'pointer');
You can always pass symphony2 variables on javascript code, so you can have a scirpt with something along the lines of:
<scirpt>
$(.glyphicon).click(function(){
$(.Some-other-class).toggle()
});
</script>
You can have the .Some-other-class div element or td element starting as hidden and with variables in it (like you did in your static html code).
You don't need the css pointer in the js use it in a css class to have it when the page load not when the user click.
And for your click you can do something like this if you photo is a link then :
first use in your twig a <img src="{{row.photo}}" alt="" style="display:none;"/> whenever you want to put the images.
then inside your js in the click photo button
$('.show-hide-photo').on('click', function(){
$(this).closest('tr').find('img').toggle();
});
$(this) is your show-hide-photo button , closest('tr') will look for the tag which contain your button then find('img') will go find the tag inside that (row) in that case you wont need to bother with ids to select right row etc..

Hide table header if there is no row

I have a table with patients in which, for each row of table I can remove the row or do other operations, so when I remove all the rows I want my table header to be hidden or removed.
<table id ="results-table" class="table table-strip">
<thead>
<tr>
<th>Emri</th>
<th>Mbiemri</th>
<th>Numri personal</th>
<th>Vendi i lindjes</th>
<th>Data e diagnozës së parë</th>
<th>Data e raportimit</th>
<th>Mjeku raportues</th>
<th>Veprimet</th>
</tr>
</thead>
{% if patient_docs and patient_docs.collection.count() > 0 %}
<tbody id="patient-list">
{% for patient_doc in patient_docs %}
<tr>
{% if patient_doc.patient is defined %}
<td>{{ patient_doc.patient.emri }}</td>
<td>{{ patient_doc.patient.mbiemri }}</td>
<td>{{ patient_doc.patient.numri_personal }}</td>
<td>{{ patient_doc.patient.vendi_lindjes }}</td>{% endif %}
<td>{% if patient_doc.diagnosis is defined %}{{ patient_doc.diagnosis.data_diagnozes_se_pare }}{% endif %}</td>
<td>{% if patient_doc.treatment is defined %}{{ data_e_raportimit }}{% endif %}</td>
<td>{% if patient_doc.treatment is defined %}{{ patient_doc.treatment.mjeku_raportues }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
{% endif %}
</table>
So how can I do that using jQuery, so that if there is no row hide the header and and show a message there is no patient registered?
You can put the line
{% if patient_docs and patient_docs.collection.count() > 0 %}
before the "thead"
OR
with jquery in a document.ready, you can check the lenght
if ($('#results-table > tbody > tr').length == 0){
$('#results-table > thead > th').css('display','none');
}
everytime u remove a row u can count the number of rows left using
var rowCount = $('#myTable tbody tr').length;
if rowcount goes to 0 then just hide the table using .hide()
in your add and remove functions you can add method like renderBody which will be hide or show your tbody part:
function addRow() {
...
renderBody();
}
function removeRow() {
...
renderBody();
}
function renderBody() {
var $tbody = $('#patient-list');
if (patient_docs && patient_docs.collection.count() > 0) {
$tbody.show();
}
else {
$tbody.hide();
}
}
Noticed you actually want to basically hide the entire table (not just the header) and show a message saying that no patients are registered.
Easy way to do this would be to create a function, check if rows exist, if they do then hide message, show table, otherwise do the opposite
var $msg = $('#msgDivId');
function showMsgOnEmptyTable(target, msg){
var $target = $(target);
if( $target.find('tr') ){
$target.show( );
$msg.hide();
} else {
$target.hide();
$msg.show();
}
}
in html
<div id="msgDivId">
No Patients Registered
</div>
<table id="targetTable"> ... table stuff goes here ... </table>
And when ever you make changes to the table call
showMsgOnEmptyTable('#targetTable');

Categories

Resources