Django endless pagination on scroll in case of overflowed container - django

I have the following Django template:
<div id = 'test'>
<form>
<ul id = 'dialog_list_container'>
{% include page_template %}
{% block js %}
{{ block.super }}
<script src="/static/js/el-pagination.js"></script>
<script src="/static/js/el-pagination_on_scroll.js">
</script>
<script>
$("#test").endlessPaginate({
paginateOnScroll: true,
paginateOnScrollMargin: 100
});
</script>
{% endblock %}
</ul>
<div class="ajax_loader"></div>
</form>
</div>
test is a scrollable element:
#test{
overflow-y: auto;
height: 500px;
}
The problem is that I have still to manually press the Show more link in the bottom of paginated list, whereas I would expect new records to be uploaded automatically as I scroll to the bottom of test. I guess, the reason is that Django's pagination on scroll triggers in those cases when browser window is scrolled.
Is there any painless way to make Django upload new records in case of a container with css overflow-y: auto attribute?

$.endlessPaginate binds to the entire page. If you want to focus on one element, you need to target that element specifically:
$("#test").endlessPaginate({
paginateOnScroll: true,
paginateOnScrollMargin: 20
});
P.S. the standard for ids (and classes) is hyphens not underscores: #dialog-list-container

Related

How do I render all the objects of a child model belonging to a particular object of the parent model on click?

Suppose there are two models Lists and Tasks where 'Lists' have one-to-many relationship with 'Tasks'.
All the objects of the Lists model are rendered on the page like this:
HTML
<div class="grid-container">
{% for list in lists %} <!--lists is context for Lists.objects.all() -->
<div class="grid-item" id="{{ list.id }}" onclick="showTasks( {{ list.id }} )">
{{ list.name }}
</div>
{% endfor %}
</div>
<dialog id="tasks">
</dialog>
JavaScript
<script>
function showTasks(listid){
document.getElementById("tasks").show();
}
</script>
Now I want to render all the tasks_set (all the objects of 'Tasks') related to a particular object of 'Lists' in that dialog with id="tasks".
As it can be seen in the above snippet, I thought of doing it by passing list.id as a parameter to the JavaScript function but couldn't figure out beyond it. How can I achieve it?
Solution 1:
Building on Iain's comment some code. I think it is easiest if you use jQuery so load it in the section of your html template, e.g.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
Then you need a view that returns the required tasks data
def tasks_view(request):
list_id = request.GET.get('listid') # fetch the id of the list
tasks = Lists.objects.get(pk=list_id).tasks.all() # get your tasks
data = {'tasks': render_to_string('tasks.html', {'tasks': tasks})} # pre render the data
return JsonResponse(data)
A remark about the view: You can of course also return the raw Json data. However, in your case I think it is easier to create a small sub-template (tasks.html in the example) and use render_to_string to get the html code you can simply add to your base html page. Don't forget to add the view to your urls.
An example task.html just for the completeness:
<ul>
{% for task in tasks %}
<li>{{ task }}</li>
{% endfor %}
</ul>
Then send an Ajax request to the view (tasks_view) when clicked.
<script>
function showTasks(listid){
$.ajax({
url: '/tasks/', // url of the view created above
data: {
'listid': listid // Your list id
},
data_type: 'html', // as we are receiving a html template
success: function(data){
$('#tasks').append(data.tasks); // append the html code to the dialog
$('#tasks').show();
}
});
}
</script>
Solution 2
In case you do not want to use Ajax and do not mind rendering all your tasks on loading the template you can also create a for the tasks of each list and show them on demand. For this iterate through your lists:
{% for list in lists %}
<dialog id="list-{{ list.id }}">
<ul>
{% for task in list.tasks.all %}
<li>{{ task }}</li>
{% endfor %}
</ul>
</dialog>
{% endfor %}
As you can see you can access the m2m field 'tasks' of each Lists object with list.tasks.all (no ()!). And each has got an individual id.
And then just show and hide the dialogs (just as an example w/o jQuery in case you are reluctant to use it):
<script>
function showTasks(listid){
// Close all dialogs
var all_dialogs = document.getElementsByTagName('dialog');
for (i = 0; i < all_dialogs.length; i++){
all_dialogs[i].removeAttribute('open');
}
// Open the required dialog
var dialog = document.getElementById("list-" + listid);
dialog.setAttribute('open','open');
};
</script>

Using different content object passed from Views to Template depending on what button user presses

I currently have one model called Comments.
After entering in a youtube Channel in a form, the user is taken to the index template that shows all the comments on that youtube channels videos that includes one of three key words (Keyword A, Keyword B , Keyword C).
I would like to add a feature so there are three links/buttons on the top of the page (each for one of the keywords).
The user can press that link and without page reload (does this mean I will need AJAX?) see the comments with that keyword, instead of all comments with any of the three keywords.
I am currently sending four content variable objects from views to the template (one with all the comments and three other objects each that just contain the comment objects for that keyword).
So the template has access to the data I need, I just need to make it so that when one of the links/buttons are clicked, it only shows that content.
Views
def addTodo(request):
new_item =Channel(channel=request.POST['channel'])
#if channel exists render page with comments
if Channel.objects.filter(channel=new_item.channel).exists():
channel_obj=Channel.objects.get(channel=request.POST['channel'])
comments_object=Comments.objects.filter(channel=channel_obj)
comments_objectA=Comments.objects.filter(channel=channel_obj, key="keywordA")
comments_objectB=Comments.objects.filter(channel=channel_obj, key="keywordB")
comments_objectC=Comments.objects.filter(channel=channel_obj, key="keywordC")
return render(request, 'todo/index.html', {'comments_all': comments_object, 'commentsa': comments_objectA,'commentsb': comments_objectB,'commentsc': comments_objectC})
Index Template
#three buttons/links on top to allow user to sort..the part Im not sure how to do:
<button type="button"onclick="justshowrelatedcomment>KeywordA!</button>
<button type="button"onclick="justshowrelatedcomment>KeywordB</button>
<button type="button" onclick="justshowrelatedcomment>KeywordC</button>
#the comment structure, would want to replace comments_all with whatever button is clicked on.
<div class="new_comment">
<!-- build comment -->
{%for a in comments_all%}
<ul class="user_comment">
<!-- current #{user} avatar -->
<!-- the comment body --><div class="comment_body">
<p>{{ a.question }}</p>
</div>
</ul>
{% endfor %}
</div>
</div>
I'm quite stuck.. Is this possible without Ajax?
If Ajax is my only/best option, how should I go about that?
I was using this solution to avoid ajax, as I couldn't figure out how to use ajax.
Thanks and cheers.
You can do it without ajax, but it does not reduce the size of loaded page. The non-ajax client-side solution is to assign each comment element a class or attribute based on its keyword - here you have used a <ul> element with user_comment class already.
So try to put the keyword as an attribute or class to your comment divs. Then you can select and then hide or show each keyword class of comments using javascript coding.
It can be done in different ways but it would be easy using pure js so I am putting the template for the implementation:
<!-- toggling comments -->
<script>
function toggle_keyword(keyword) {
document.querySelectorAll('.user_comment').forEach(function (e) {
e.style.display = 'none';
});
document.querySelectorAll('.' + keyword).forEach(function (e) {
e.style.display = 'block';
});
}
</script>
<button type="button" onclick="toggle_keyword('KeywordA');">hide/show KeywordA!</button>
<button type="button" onclick="toggle_keyword('KeywordB');">hide/show KeywordB</button>
<button type="button" onclick="toggle_keyword('KeywordC');">hide/show KeywordC</button>
#the comment structure, would want to replace comments_all with whatever button is clicked on.
<div class="new_comment">
<!-- build comment -->
{%for a in comments_all%}
<ul class="user_comment {{ a.key }}">
<!-- current #{user} avatar -->
<!-- the comment body -->
<!-- here I added a.key as a class to the div -->
<div class="comment_body">
<p>{{ a.question }}</p>
</div>
</ul>
{% endfor %}
</div>
</div>
Definitely Ajax will be best option for you. You just need to post one flag while clicking button with using ajax. On that flag basis you can decide which data you want to pass for your html
HTML
<button onclick="justshowrelatedcomment('A')">KeywordA</button>
<button onclick="justshowrelatedcomment('B')">KeywordB</button>
<button onclick="justshowrelatedcomment('C')">KeywordC</button>
<script>
function justshowrelatedcomment (flag) {
$.ajax({
url: 'addTodo',
type: 'POST',
data: {
flag: flag
},
success: function(data){
return data;
}
});
}
</script>
View
def addTodo(request):
flag = request.POST['flag']
new_item =Channel(channel=request.POST['channel'])
Hopefully this will work for you.

How to call a django url after clicking on a bootstrap tab and show results on the tab content?

I have created two bootstrap tabs whose data is comming from django functions. I can easily post essential data of all tabs while the tabs are loaded but I want to load data of each tab once the tab is clicked instead of loading all data together.
Bootstrap uses href or data-target to load a tab content. So, when I set django url in href attribute and activate the tab by JavaScript, the django function is invoked but bootstrap does not open the tab. It always opens the first tab.
<ul id="myTab" class="nav nav-tabs bar_tabs" role="tablist">
<li role="presentation" class="active">Profile
</li>
<li role="presentation" class="">Monitor
</li>
</ul>
<div id="myTabContent" class="tab-content">
<div role="tabpanel" class="tab-pane fade active in" id="tab_content1" aria-labelledby="home-tab">
</div>
<div role="tabpanel" class="tab-pane fade" id="tab_content3" name="tab_content3" aria-labelledby="profile-tab">
{% include "setting/monitor.html" %}
</div>
</div>
My JavaScript code is:
<script>
$(document).ready(function(){
switch("{{section}}"){
case 'monitor':
$('#myTabContent #tab_content3').tab('show');
break;
case 'profile':
$('#myTab a[href="#tab_content1"]').click();
break;
}
})
</script>
I have also used the below javascript to activate second tab, but it does not wok.
$("#myTab").tabs("select", 2);
I appreciate your help.
Bootstrap will show the tab which you marked as "active". So when returning the HTML for 2nd tab make that tab as active by adding class="active" attribute to it.
May be you need to pass that information from view in the context and add that class to appropriate tab in the template.

Display Form Field based on other Fields within the Django Form

I am trying to create a way to only display certain fields within a django form based on the bound data of another field within that same form. I'm familiar with the idea of form.field.bound_type but I'm not sure how to continually check for state change on a field in a form and update the other field accordingly. Something like if you were filling out an application and it asked if you've committed a crime, if you click yes then a details text area pops up.
I'm using:
Django 1.8.4
Bootstrap3 6.6.2
As it pertains to this question. Here is what I've currently got with the field values edited for work protection. It does SORT of work. Meaning the form is fine, the if statement works initially but it doesn't reevaluate the if once the specified field has changed.
<form action= "/send_email/" method="post" class='col-sm-5'>
{% csrf_token %}
{% bootstrap_field form.field_one%}
{% bootstrap_field form.field_two%}
{% bootstrap_field form.field_three%}
{% if form.field_three.bound_data == "A Value" %}
{% bootstrap_field form.field_four%}
{% endif %}
{% buttons %}
<button type="submit" class="btn btn-primary">
{% bootstrap_icon "glyphicon glyphicon-ok-circle" %} Submit
</button>
{% endbuttons %}
</form>
Solution:
With Birdie's help I was able to figure out the solution. For anyone who has hit this same Django related problem here is how you add or remove fields based on another field in the same form.
<script>
// function that hides/shows field_four based upon field_three value
function check_field_value(new_val) {
if(new_val != 'A value') {
// #id_field_four should be actually the id of the HTML element
// that surrounds everything you want to hide. Since you did
// not post your HTML I can't finish this part for you.
$('#field_four').removeClass('hidden');
} else {
$('#field_four').addClass('hidden');
}
}
// this is executed once when the page loads
$(document).ready(function() {
// set things up so my function will be called when field_three changes
$('#field_three').change( function() {
check_field_value(this.value);
});
});
</script>
<form action= "/send_email/" method="post" class='col-sm-5'>
{% csrf_token %}
{% bootstrap_field form.field_one%}
{% bootstrap_field form.field_two%}
<div id="field_three">{% bootstrap_field form.field_three%}</div>
<div id="field_four">{% bootstrap_field form.additional_notes %}</div>
{% buttons %}
<button type="submit" class="btn btn-primary">
{% bootstrap_icon "glyphicon glyphicon-ok-circle" %} Submit
</button>
{% endbuttons %}
</form>
You cannot do this within the template because the template is executed on the server side, but the user interaction occurs on the client side.. in the browser. This must be done in javascript and run in the browser.
Here is an example of jQuery code which does this. I did not test it so it may need tweaking, but this should get you in the right direction.
You will need to look at your HTML to determine the id of the element you actually want to hide() and show(). Normally you would have some kind of HTML element (eg. a DIV) surrounding both the field or fields you want to hide as well as the label(s).. and you would hide everything at once by hiding the element which contains all the fields, rather than each individual field itself.
If you add the HTML surrounding field_four to your question, I will update the answer to work with what you've got...
<script>
// Ideally this script (javascript code) would be in the HEAD of your page
// but if you put it at the bottom of the body (bottom of your template) that should be ok too.
// Also you need jQuery loaded but since you are using bootstrap that should
// be taken care of. If not, you will have to deal with that.
// function that hides/shows field_four based upon field_three value
function check_field_value() {
if($(this).val() == 'A Value') {
// #id_field_four should be actually the id of the HTML element
// that surrounds everything you want to hide. Since you did
// not post your HTML I can't finish this part for you.
$('#id_field_four').hide();
} else {
$('#id_field_four').show();
}
}
// this is executed once when the page loads
$(document).ready(function() {
// set things up so my function will be called when field_three changes
$('#id_field_three').change(check_field_value);
// set the state based on the initial values
check_field_value.call($('#id_field_three').get(0));
});
</script>
Thanks all for the contribution, but I could not get the code above work. This is my solution:
<script>
function hideField() {
check = document.getElementsByName("put your field name here")[0].value;
response = document.getElementById("put your id here");
if (check === "Open") {
response.style.display = "none";
}else{
response.style.display = "block";
}
}
</script>
The key is to figure out Id for the field you want to hide, and name for the field you check. I did NOT use DIV container to assign the id and the name, but inspected rendered HTML page with developer tools in the browser. Let me know if you have questions or want more detailed explanation.
I found this question very interesting. I have updated it with bootstrap 4.0v, with the following script, I hope it can help someone:
<script>
// function that hides/shows field_four based upon field_three value
function check_field_value(new_val) {
if(new_val != 'A value') {
// #id_field_four should be actually the id of the HTML element
// that surrounds everything you want to hide. Since you did
// not post your HTML I can't finish this part for you.
$('#field_four').removeClass('d-none');
} else {
$('#field_four').addClass('d-none');
}
}
// this is executed once when the page loads
$(document).ready(function() {
// set things up so my function will be called when field_three changes
$('#field_three').change( function() {
check_field_value(this.value);
});
});
</script>
<form action= "/send_email/" method="post" class='col-sm-5'>
{% csrf_token %}
{% bootstrap_field form.field_one%}
{% bootstrap_field form.field_two%}
<div id="field_three">{% bootstrap_field form.field_three%}</div>
<div id="field_four">{% bootstrap_field form.additional_notes %}</div>
{% buttons %}
<button type="submit" class="btn btn-primary">
{% bootstrap_icon "glyphicon glyphicon-ok-circle" %} Submit
</button>
{% endbuttons %}
</form>

Customize count list

I have this code I'm using to generate a list of records categorized into year, make, series, body style, and color for vehicles. I'd like to customize this further this way:
for the year, I want to have only up to 2004 being individual...the rest will fall under other i.e. 2009, 2008, 2007, 2006, 2005, 2004, Other.
for the make, I want to display the six makes with the highest popularity...there's a field in the model I'm using to assign the popularity of a make with a value of primary (highest), secondary or tertiary. The rest will fall under Other.
For the body style and color, I want to have the items having less than 3 records falling under Other.
My code is as below:
year_count = vehicle_query.order_by(
'-common_vehicle__year__year').values('common_vehicle__year__year').
annotate(count=Count('id'))
make_count = vehicle_query.order_by(
'common_vehicle__series__model__manufacturer__manufacturer').
values('common_vehicle__series__model__manufacturer__manufacturer').
annotate(count=Count('id'))
style_count = vehicle_query.order_by(
'common_vehicle__body_style__style').values
('common_vehicle__body_style__style').annotate(count=Count('id'))
colour_count = vehicle_query.order_by(
'exterior_colour__exterior_colour').values(
'exterior_colour__exterior_colour').annotate(count=Count('id'))
The bulk of what you're asking would probably better be handled outside of Django and instead by client-side javascript. To be clear, you could have portions handled by Django, but it would be cleaner not doing so. There are benefits to doing it this way:
Your Django template code stays cleaner
It will degrade nicely
You can later update the interface (change the javascript) and not have to worry about breaking the Django template
To handle this you could simply make a script that when given a <ul> tag (and maybe some arguments) will render that list in the format you're asking about.
Here's a simple example using jQuery. For this example, I'm going to wrap the functionality in a using a jQuery plugin pattern.
Say your django template outputs the following...
<ul>
<li>Chevy</li>
<li>Mazda</li>
<li>Honda</li>
<li>Ford</li>
<li>BMW</li>
</ul>
jquery.showmorelist.js
(function($) {
$.fn.ShowMoreList = function(visibleItemCount) {
// Wrap parent element
var parent = $(this).wrap('<div class="show-more-list"></div>').parent();
var ul = $(this);
// Enumerate children and hide extras
var counter = 0;
$(this).children().filter('li').each(function(){
counter += 1;
if (counter > visibleItemCount) {
$(this).hide();
$(this).addClass('hidden');
}
});
// Add link and bind click
var link = $('> Show More').click(function(){
$(ul).children().filter('.hidden').show();
});
$(parent).append(link);
}
})(jQuery);
page.html
<script type="text/javascript" src="jquery.showmorelist.js"></script>
<script type="text/javascript">
// On page load...
$(function() {
$('ul').ShowMoreList(4); // Shows only the first 4 items
});
</script>
This is a rather simple example, and it won't switch the "Show More" to "Hide More" but you should be able to figure that out from the context.
I managed to get a solution, so I thought it'd be good to update the answer here:
In the head section I have this:
<script type="text/javascript" src="{{ MEDIA_URL }}share/jquery/jquery.min.js"></SCRIPT>
<script type="text/javascript">
(function($) {
$(document).ready(function() {
//hide the additional content under "Display More"
$("div.additional_content").hide();
$("a.more").click(function () {
//show or hide the additional content
$(this).siblings("div.additional_content").toggle();
//change the attributes and text value of the link toggle
if($(this).text() == "Display Less"){
$(this).removeClass("less");
$(this).addClass("more");
$(this).html("Display More");
}else{
$(this).removeClass("more");
$(this).addClass("less");
$(this).html("Display Less");
}
return false;
});
});
})(jQuery);
Then wherever I want to reduce the number of available options I have this:
<div class="module_wrap">
<div class="module"> {% if year_count %} <strong>{% trans "Year" %}</strong> <br />
{% for item in year_count|slice:":6" %}
<ul>
<li> {{ item.common_vehicle__year__year }} ({{ item.count }}) {% if request.session.chosen_year %} <img src="{{ MEDIA_URL }}img/undo.gif" border="0" alt="Remove Year Filter" title="Remove Year Filter" /> {% endif %} </li>
</ul>
{% endfor %}
<div class="additional_content"> {% for item in year_count|slice:"6:" %}
<ul>
<li> {{ item.common_vehicle__year__year }} ({{ item.count }})</li>
</ul>
{% endfor %} </div>
{% if year_count|slice:"6:" %}Display More<br />
{% endif %} <br />
</div>
</div>
{% endif %}