Dynamically add or remove table rows with jQuery or Angular? - javascript

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..

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()),
]

Changing elements' CSS using jQuery after another element is changed with AJAX

I have a custom button in the a header next to a "cart" link. Website uses AJAX cart. When an item is added to cart, the cart link enlarges to include nr of items in cart. This causes the cart link to overflow and overlap the custom button next to it. Essentially what I'm trying to achieve is to add a margin to the custom button when an item is added to cart and the cart link is expanded. My first thought was to wrap both in a div and use CSS flex to adjust the sizing of both, however, and please correct me if I'm wrong, I thought it would be quicker to just write a script that would add a margin to my custom button. Definitely didn't turn out to be the quicker way as I've been stuck on it for hours now - any help would be greatly appreciated. I've browsed through similar posts and couldn't find a result that I want - any help would be greatly appreciated (I'm still new to JS and jQuery but learning). Below is the code and some of the things that I've tried.
HTML - header
<div class="header--menu">
<div class="custom-btn-container">
Quick Order
</div>
{%
render 'framework--x-menu',
js_class: 'XMenu',
align: menu_alignment,
overlap_parent: 1,
handle: menu
%}
</div>
{% endif %}
<div class="header--cart">
{% render 'snippet-cart', cart_icon: cart_icon %}
</div>
</div>
HTML - cart
{% if settings.cart--type == 'drawer' %}
<div
class="cart--open-right off-canvas--open"
data-off-canvas--open="right-sidebar"
data-off-canvas--view="cart"
aria-haspopup=”menu”
>
{% endif %}
<a
class="header--cart-link"
data-item="accent-text"
href="{{ routes.cart_url }}"
aria-label="{{ 'layout.header.cart' | t }}"
>
{% if cart_icon == 'text' %}
{{ 'layout.header.cart' | t }}
{% elsif cart_icon == 'bag' %}
{% render 'framework--icons', icon: 'bag' %}
{% else %}
{% render 'framework--icons', icon: 'cart' %}
{% endif %}
<span class="header--cart-number" data-item-count="{{ cart.item_count }}">
(<span class="cart--external--total-items">{{ cart.item_count }}</span>)
</span>
</a>
{% if settings.cart--type == 'drawer' %}
</div>
{% endif %}
And these are some of the scripts that I've tried:
window.onload = () => {
if (jQuery('.header--cart-number').data('item-count') != '0') {
$(".custom-button").css('margin-right', '10px');
}
}
The above is nearly what I'm after, but only works after refreshing the page.
document.ajaxComplete = () => {
if (jQuery('.header--cart-number').data('item-count') != '0') {
$(".custom-button").css('margin-right', '10px');
}
}
similar story
$(document).ajaxSuccess(function() {
if (jQuery('.header--cart-number').data('item-count') != '0') {
$(".custom-button").css('margin-right', '10px');
}
}
This didn't do anything whatsoever.
I'm sure I'm missing something small, but it's driving me mad. Again, any help will be greatly appreciated!
Best,
J

Show and hide text of different posts

I have several posts each of them composed of three parts : a title, a username/date and a body. What I want to do is to show the body when I click on either the title or the username/date and hide it if I click on it again. What I've done so far works but not as expected because when I have two or more posts, it only shows the body of the last post even if I click on another post than the last one. So my goal is only to show the hidden text body corresponding to the post I'm clicking on. Here is my code:
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Test page{% endblock %}</h1>
<a class="action" href="{{ url_for('main_page.create') }}">New</a>
{% endblock %}
{% block content %}
{% for post in posts %}
<article class="post">
<header>
<script language="JavaScript">
function showhide(newpost)
{var div = document.getElementById(newpost);
if (div.style.display !== "block")
{div.style.display = "block";}
else {div.style.display = "none";}}
</script>
<div onclick="showhide('newpost')">
<h1>{{ post['title'] }}</h1>
<div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%d-%m-%Y') }}</div>
</div>
</header>
<div id="newpost">
<p class="body">{{ post['body'] }}</p>
</div>
</article>
{% if not loop.last %}
<hr>
{% endif %}
{% endfor %}
{% endblock %}
Of course I looked for a solution as much as I could but I'm kind of stuck plus I'm a complete beginner in HTML/JS/CSS. And one last thing, I'm currently using Python's framework Flask. Thank you by advance.
You need to give each of your posts a unique id for your approach to work.
Change your code to
<div id="{{post_id}}">
<p class="body">{{ post['body'] }}</p
</div>
where post_id is that post's unique id e.g. its id in the database you are using that you pass to the template in your view. Then, change the call to the onclick event handler to
<div onclick="showhide('{{post_id}}')">
If you don't have a unique id you can also use the for loop's index: replace all post_id instances above with loop.index. See Jinja's for loop docs for more information.

create table with dynamic rows and columns with django html template

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.

Categories

Resources