Modal opening empty because of display null - javascript

(I know, bad title, pardon me)
So here's the thing:
I have a navbar with 3 icons and each one opens the same modal, but with different resources inside.
They are only clickable if resources exist for them, otherwise they are grayed out. Therefore 0, 1, 2 or all 3 of them can be visible or grayed out.
It works fine when all 3 are clickable, but if 1 or 2 are grayed out, the other opens an empty modal and the console shows assistance.js:29 Uncaught TypeError: Cannot read property 'style' of null.
This is how the twig looks and how the modal is populated and the modal itself:
<div class="assistance-body">
<nav>
<ul class="nav nav-tabs nav-justified nav-no-padding-top">
{% set allResources = ['videos', 'glossary', 'links'] %}
{% for key in allResources %}
{% if key in resources|keys %}
<li{% if loop.first %} ng-class="active" {% verbatim %}{{class}}{% endverbatim %} {% endif %} class="icon-resource">
<a
data-toggle=""
href="#assistance-{{ key }}"
segment-event="Modules: Tutor: Clicked {{ key|capitalize }} Section"
segment-not-track-if-class="active"
onclick="toggleAssistance('assistance-{{ key }}', ['assistance-videos', 'assistance-glossary', 'assistance-links'] )"
>
<i class="icon-{{ key }} icon-sm"></i>
<span class="sr-only">{{ ('resources.tabs.' ~ key ~ '.title') | trans }}</span>
</a>
</li>
{% else %}
<li{% if loop.first %} {% verbatim %}{{class}}{% endverbatim %} {% endif %} class="icon-resource">
<i class="icon-{{ key }} icon-sm icon-sm-gray"></i>
<span class="sr-only">{{ ('resources.tabs.' ~ key ~ '.title') | trans }}</span>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>
<div id="assistance" class="assistance">
<div>
{% for key, data in resources %}
<div id="assistance-{{ key }}">
<button
type="button"
class="close"
onclick="toggleAssistance('assistance-{{ key }}', ['assistance-videos', 'assistance-glossary', 'assistance-links'] )"
aria-hidden="true">
×
</button>
{% include data.view with { 'category': data.category, 'resources': data.resources } %}
</div>
{% endfor %}
</div>
</div>
</div>
And this is the assistance.js:
function toggleAssistance (target, all) {
var targetAssistanceResource = document.getElementById(target);
var allAssistanceResources = [];
all.forEach(function (el) {
allAssistanceResources.push(document.getElementById(el));
});
// the resource is already opened, let's close it
if (getStyle(targetAssistanceResource, 'display') !== 'none') {
// hiding the main assistance div
document.getElementById('assistance').style.display = 'none';
// hiding all assistance resources
allAssistanceResources.forEach(function (res) {
res.style.display = 'none';
});
} else { // it's not opened, let's open it
// showing the main assistance div
document.getElementById('assistance').style.display = 'block';
// hiding all assistance resources
allAssistanceResources.forEach(function (res) {
res.style.display = 'none';
});
// showing the target assistance resource
targetAssistanceResource.style.display = 'block';
}
}
function getStyle (el, cssprop) {
if (el.currentStyle) { // IE
return el.currentStyle[cssprop];
} else if (window.getComputedStyle) {
return window.getComputedStyle(el)[cssprop];
}
// finally try and get inline style
return el.style[cssprop];
}

Related

How to link pagination to search Form, ERROR: it is saying "PAGE" is not defined

It keep on saying:
"PAGE" is not defined.
My code:
{% if queryset.has_other_pages %}
<div class="pagination">
<ul class="container">
{% if queryset.has_previous %}
<li><a href="?page={{queryset.previous_page_number}}" class="btn page-link"
data-
page="{{queryset.previous_page_number}}">❮ Prev</a></li>
{% endif %}
{% for page in custom_range %}
{% if page == queryset.number %}
<li><a href="?page={{page}}" class="btn page-link btn--sub" dtata-page=
{{page}}">{{page}}</a></li>
{% else %}
<li><a href="?page={{page}}" class="btn page-link" dtata-page="{{page}}">
{{page}}</a></li>
{% endif %}
{% endfor %}
<!-- The below code works for Next button -->
{% if queryset.has_next %}
<li><a href="?page={{queryset.next_page_number}}" class="btn page-link"
data-page="{{queryset.next_page_number}}">Next ❯</a></li>
{% endif %}
</ul>
</div> {% endif %}
<script type="text/javascript">
//Get Page Search form and page links
let searchForm = document.getElementById('searchForm')
let pageLinks = document.getElementsByClassName('page-link')
// Ensure Search Form Exist
if(searchForm){
for (let i = 0; pageLinks.length > i; i++){
pageLinks[i].addEventListener('click', function (e) {
e.preventDefault()
//Get The Data Attribute
let page= this.dataset.page
//Add Hideen Search Input To Form
searchForm.innerHTML += `<input value=${page} name="page" hidden/>`
searchForm.submit()
})
}
} </script>

Scrolldown after clicking button javascript

For my website, i would like to scrolldown automatically to an article when the user click on a button category.
Here is my twig code.
`
{% if category is not null and category|length > 0 %}
{% for categorie in category %}
<li class="nav-item category-link">
<button class="nav-link active" categoryId="{{categorie.id}}">{{categorie.name}}</button>
</li>
{% endfor %}
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="">
{% for content in contents %}
<div contentId="{{content.id}}">
<h2 class="text-center">{{content.title}}</h2>
</div>`
And this is my JS part.
var contentId = document.querySelector('div').getAttribute('contentId');
var categoryId = document.querySelector("button").getAttribute('categoryId');
if(categoryId){
categoryId.addEventListener('click', function() {
if (contentId === categoryId){
window.scrollTo(contentId);
}
});
}```
For information, actually in my database contentId related to categoryId have the same value. But for future they may not have the same value.
Thanks a lot for your help.
How about a non-JS solution?
To do this with JS you would have to do a querySelectorAll on all of the buttons and add an event listener on each, then query the respective contentId div then scroll to it.
A non-JS solution can be like this:
{% if category is not null and category|length > 0 %}
{% for categorie in category %}
<li class="nav-item category-link">
{{categorie.name}}
</li>
{% endfor %}
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="">
{% for content in contents %}
<div id="{{content.id}}" contentId="{{content.id}}">
<h2 class="text-center">{{content.title}}</h2>
</div>
const contents = document.querySelectorAll('div[contentId]')
const categories = document.querySelectorAll("button[categoryId]")
categories.forEach(category => {
category.addEventListener('click', function() {
const categoryId = category.getAttribute('categoryId');
const content = Array.from(contents).find(content => content.getAttribute('contentId') === categoryId)
if (content){
content.scrollIntoView({ behavior: "smooth" });
}
)}
});

sorting messages with tag in django template

I'm receiving a list of messages, each message has a tag. It may be "info", "warning", "danger" and "spreadsheet". I need to display the first three first and then spreadsheet's but in collapsible if there are more than 3 of them.
So for all messages BUT spreadsheet the template is:
<div class="row banner">
<ul class="banner-message">
{% for message in messages %}
<li{% if message.tags %} class="banner-message--{{ message.tags }}" {% else %} class="banner-message--info" {% endif %}>
<div class="message-content">
<p>{{ message|safe }}</p>
<span class="close">×</span></div>
</li>
{% endfor %}
</ul>
</div>
For tags with "spreadsheet" tag the template is:
<div class="row banner">
<ul class="banner-message">
{% for message in messages %}
<li class="{% if forloop.counter >= 3 %}collapsible_content{% endif %} banner-message--info">
<div class='message-content'>
<p>{{ message|safe }}</p>
<span class="close">×</span>
</div>
</li>
{% endfor %}
{% if messages|length > 3 %}
<button id="show_more" class="btn btn-secondary collapsible_button">Show all</button>
{% endif %}
</ul>
</div>
Where button is shown for spreadsheet messages if there are more then 3 of them and shows all of them on click.
Problem is that I receive these messages in one array and I have no guarantee that they won't be all mixed up in different order.
I need to sort them somehow with message.tags to two separate arrays? Or use some smart if but I cannot figure out how to achieve that in the template. Could you please help?
EDIT:
After comment below I am trying to sort the messages in the view.py
m = messages.get_messages(request)
if len(m) > 0:
context['msgs'] = m.filter(tags__in=['info', 'warning', 'danger']).order_by('tags')
context['spreadsheet'] = m.filter(tags='spreadsheet')
m is FallbackStorage object and it doesn't have filter method. m.objects and FallbackStorage.objects do not work either (for the same reason: has no attribute 'objects'). I've tried to do all above with messages straight away (without the get_messages method) but the result is the same. How to filter this correctly?
get_messages is a utility function and not a class method. You can read more about how to use it in the docs here.
In your view you can do:
from django.contrib.messages import get_messages
...
message_list = get_messages(request)
messages = []
spreadsheet_messages = []
for message in message_list:
tags = message.tags
if ("info" in tags) or ("warning" in tags) or ("danger" in tags):
messages.append(message)
if "spreadsheet" in tags:
spreadsheet_messages.append(message)
context["messages"] = messages
context["spreadsheet"] = spreadsheet_messages
...
you can divide messages in two different querysets. On your views.py:
context['messages'] = messages.filter(tags__in = ['info','warning','danger']).order_by('tags')
context['spreadsheet'] = messages.filter(tags = 'spreadsheet')
On your template:
<div class="row banner">
<ul class="banner-message">
{% for message in messages %}
<li{% if message.tags %} class="banner-message--{{ message.tags }}" {% else %} class="banner-message--info" {% endif %}>
<div class="message-content">
<p>{{ message|safe }}</p>
<span class="close">×</span></div>
</li>
{% endfor %}
</ul>
</div>
<div class="row banner">
<ul class="banner-message">
{% for message in spreadsheet %}
<li class="{% if forloop.counter >= 3 %}collapsible_content{% endif %} banner-message--info">
<div class='message-content'>
<p>{{ message|safe }}</p>
<span class="close">×</span>
</div>
</li>
{% endfor %}
{% if messages|length > 3 %}
<button id="show_more" class="btn btn-secondary collapsible_button">Show all</button>
{% endif %}
</ul>
</div>
EDIT:
According to you comment. I suggest to try something like this:
msgs = []
spreadsheet = []
m = messages.get_messages(request)
if len(m) > 0:
for mensaje in m:
#TRY THE THREE OPTIONS BECAUSE I DON'T KNOW THE MESSAGES' STRUCTURE#
if 'spreadsheet' in message['tags']:
#if 'spreadsheet' in message.tags:
#if 'spreadshhet' in message.tags():
spreadsheet.append(mensaje)
else:
msgs.append(mensaje)
context['msgs'] = msgs
context['spreadsheet'] = spreadsheet

infinite scroll working but not populating all data because of javascript call

In my web site I want to show the user ratings - for that I used the infinite scroll but I am facing one problem.
When it first loads the data before calling the <a class="infinite-more-link" href="?page={{ ratings.next_page_number }}"></a> it is showing the star with the count of vote,but when after calling the <a class="infinite-more-link" href="?page={{ ratings.next_page_number }}"></a> it is not showing the star.
my views.py
#login_required
def ratings_user(request,pk):
ratings = VoteUser.objects.filter(the_user_id=pk).order_by('-pk')
paginator = Paginator(ratings, 1)
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
posts = paginator.page(1)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
return render(request,request.session['is_mobile']+'profile/ratings.html',{'ratings':posts})
html
{% extends 'mobile/profile/base.html' %}
{% block title %}
Ratings
{% endblock %}
{% block leftcontent %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" rel="stylesheet">
{% endblock %}
{% block middlecontent %}
<div class="infinite-container">
{% for i in ratings %}
<div class="infinite-item">
<div class="w3-container w3-card w3-white w3-round w3-margin">
<img src="{{ i.the_user.profile.avatar.url }}" alt="Avatar" class="w3-left w3-circle w3-margin-right" style="width:40px;height:40px;border-radius:50%;">
{% with user=i.the_user.profile %}{{ user.prenom|title|truncatewords:2 }} {{ user.nom|title|truncatewords:1 }}{% endwith %}
<br>
<span class="stars" data-rating="{{ i.vote.vote }}" data-num-stars="5" ></span>
<hr class="w3-clear">
<p>
{{ i.commentaire|linebreaksbr }}
</p>
<span class="glyphicon glyphicon-user"></span> {% with user=i.the_sender.profile %}{{ user.prenom|title|truncatewords:2 }} {{ user.nom|title|truncatewords:1 }}{% endwith %}
</div>
</div>
{% endfor %}
</div>
{% if ratings.has_next %}
<a class="infinite-more-link" href="?page={{ ratings.next_page_number }}"></a>
{% endif %}
{% endblock %}
{% block rightcontent %}
{% endblock %}
{% block js %}
<script>
var infinite = new Waypoint.Infinite({
element: $('.infinite-container')[0]
});
</script>
<script>
//ES5
$.fn.stars = function() {
return $(this).each(function() {
var rating = $(this).data("rating");
var fullStar = new Array(Math.floor(rating + 1)).join('<i class="fas fa-star"></i>');
var halfStar = ((rating%1) !== 0) ? '<i class="fas fa-star-half-alt"></i>': '';
var noStar = new Array(Math.floor($(this).data("numStars") + 1 - rating)).join('<i class="far fa-star"></i>');
$(this).html(fullStar + halfStar + noStar);
});
}
//ES6
$.fn.stars = function() {
return $(this).each(function() {
const rating = $(this).data("rating");
const numStars = $(this).data("numStars");
const fullStar = '<i class="fas fa-star"></i>'.repeat(Math.floor(rating));
const halfStar = (rating%1!== 0) ? '<i class="fas fa-star-half-alt"></i>': '';
const noStar = '<i class="far fa-star"></i>'.repeat(Math.floor(numStars-rating));
$(this).html(`${fullStar}${halfStar}${noStar}`);
});
}
</script>
<script>
$(function(){
$('.stars').stars();
});
</script>
{% endblock %}
I have tried to put the <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" rel="stylesheet"> inside the class="infinite-item" but it does not help.what might be the reason for that ? Thanks.
Since yesterday I am on this question I tried everything.
This is another user that has tried to help me here https://stackoverflow.com/a/69930878/15042684 but I did not really understand could you please help me to understand it with some code.
this is his answer:
It doesn't look like .stars() will look for new elements being added to the DOM. You should look for a callback function configuration option within Waypoint.Infinite that you can call .stars() on the new elements.
Assuming you are using waypoint's Infinite Scroll, you can use the onAfterPageLoad callback
onAfterPageLoad
Default: $.noop.
Parameters: $items.
This is a callback that will fire at the end of the request cycle, after new items have been appended to the container. It is passed one parameter, which is a jQuery object of all the items that were appended during the page load.
Note that despite using the jquery convention of $name indicates a jquery object and stating is a jquery object, in this case, trial and error shows that $items are the DOM elements, not a jquery object.
No example provided in the docs, so it will probably look something like:
<script>
var infinite = new Waypoint.Infinite({
element: $('.infinite-container')[0],
onAfterPageLoad: function(items) {
$(items).find(".stars").stars();
}
});
</script>

Some thing like browsing file in jQuery

I want to create a page, some thing like browsing a file. Now I have this question:
I have some links in this page which are folders and files, and an "Open" button. I want when user press a key, the first link (file/folder) started with this character be selected. I also want when user double-clicks on each link or if clicks on each link and press "Open" button, i show the content of selected folder or if this is file the, I want the window to be closed. Now I want to know how can I understand when "Open" button is clicked, which item is selected?
<script type="text/javascript">
function func(id){
var label = $('<label/>').text(id).appendTo($('#div_for_labels'));
$('#div_show').html('waiting...').load('learning/?ajax=true&path='+id);
}
$('openButton').click(function(e){
e.preventDefault();
???
})
</script>
<div id="div_for_labels"></div>
<div id="div_show">
{% for f_name, f_type in list %}
{% if f_type == 'folder' %}
<p><a ondblclick="func(this.id)" id="{{ f_name }}" class="res"><img src="/media/img/folder.jpeg">{{ f_name }}</a></p>
{% else %}
<p><a ondblclick="func(this.id)" id="{{ f_name }}" class="res"><img src="/media/img/file.jpeg">{{ f_name }}</a></p>
{% endif %}
{% endfor %}
</div>
<div>
<input type="submit" id="openButton" value="Open">
</div>
I don't know what script should I write on "Open" button to understand which item is selected and so send its id to my view?
EDIT:
My views.py:
def learning(request):
if request.is_ajax():
if 'path' in request.GET:
# receives list
return render_to_response("path.html",{'list': list})
else:
# receives list
return render_to_response("learningPath.html",{'list': list})
The first time it goes to "learning.html" page which contains div for labels and "open" button. The other times because of preventing from repeating div for labels and "open" button, it goes to "path.html" which only contains:
{% for f_name, f_type in list %}
{% if f_type == 'folder' %}
<p><a ondblclick="func(this.id)" id="{{ f_name }}" class="res"><img src="/media/img/folder.jpeg">{{ f_name }}</a></p>
{% else %}
<p><a ondblclick="func(this.id)" id="{{ f_name }}" class="res"><img src="/media/img/file.jpeg">{{ f_name }}</a></p>
{% endif %}
{% endfor %}
Do not mix HTML and JavaScript, just do everything with jQuery.
<script type="text/javascript">
$(function() {
var _loadFile = function(file) {
if (file.type == 'folder') {
var label = $('<label/>').text(file.name).appendTo($('#div_for_labels'));
$('#div_show').html('waiting...').load('learning/?ajax=true&path='+file.name);
} else {
// load file here
}
};
var _selectedFile = null; // nothing selected;
// using '#div_show' as relative parent, fetch all 'a.res' elements...
$('a.res', $('#div_show')).live({ // bind now and any future elements
dblclick: function() {
var $this = $(this);
_loadFile($this.data('file'));
},
click: function() {
var $this = $(this);
$('#div_show a.res.file-selected').removeClass('file-selected'); // remove old selection (if any)
$this.addClass('file-selected');
_selectedFile = $this.data('file');
}
});
$('openButton').click(function(e){
e.preventDefault();
if (_selectedFile) {
_loadFile(_selectedFile);
} else {
alert("No file selected");
}
})
});
</script>
<div id="div_for_labels"></div>
<div id="div_show">
{% for f_name, f_type in list %}
<p><a data-file="{'name':'{{ f_name }}', 'type':'{{ f_type }}'}" class="res"><img src="/media/img/{{ f_type }}.jpeg">{{ f_name }}</a></p>
{% endfor %}
</div>
<div>
<input type="submit" id="openButton" value="Open">
</div>
Note : I assume that f_type is either folder or file.
** Edit **
Your path.html file should contain only the mentioned HTML chunk like above :
{% for f_name, f_type in list %}
<p><a data-file="{'name':'{{ f_name }}', 'type':'{{ f_type }}'}" class="res"><img src="/media/img/{{ f_type }}.jpeg">{{ f_name }}</a></p>
{% endfor %}

Categories