I am working on a Django project where I want to output a text file and apply certain operations like highlighting to the text. My idea was to implement it in such a way that each word can be clicked, for a popup window (or tooltip window) to appear, displaying all the options.
{% for Line in File.body %}
<div>
{% for Token in Line %}
{% if not Token.content == None %}
<span class="inline token" id="token">
<strong style="color: {{ Token.highlight }};">
{{ Token.content }}
</strong>
</span>
<div id="{{ Token.unique_id }}">
POPUP </br>
<button onclick="changeColor()">Highlight</button>
</div>
<script>
tippy('.token', {
content: document.getElementById("{{ Token.unique_id }}"),
delay: 100,
arrow: true,
arrowType: 'round',
size: 'large',
duration: 500,
animation: 'scale',
allowHTML: true,
hideOnClick: true,
trigger: "click"
})
function changeColor() {
var token = document.getElementById("token");
token.style.color="blue;";
}
</script>
{% endif %}
{% if Token.endline %}
</br>
{% endif %}
{% endfor %}
</div>
{% endfor %}
I tried using the tippjs tooltip library (https://atomiks.github.io/tippyjs/), but clicking the highlight button doesn't do anything. If i don't put the javascript part in the loop, nothing happens at all. The code with the javascript part not in the loop would look something like this:
<script>
tippy('.token', {
content: document.getElementById("{{ Token.unique_id }}"),
delay: 100,
arrow: true,
arrowType: 'round',
size: 'large',
duration: 500,
animation: 'scale',
allowHTML: true,
hideOnClick: true,
trigger: "click"
})
function changeColor() {
var token = document.getElementById("token");
token.style.color="blue;";
}
</script>
{% for Line in File.body %}
<div>
{% for Token in Line %}
{% if not Token.content == None %}
<span class="inline token" id="token">
<strong style="color: {{ Token.highlight }};">
{{ Token.content }}
</strong>
</span>
<div id="{{ Token.unique_id }}">
POPUP </br>
<button onclick="changeColor()">Highlight</button>
</div>
{% endif %}
{% if Token.endline %}
</br>
{% endif %}
{% endfor %}
</div>
{% endfor %}
I am new to django and even newer to javascript, so I'm not sure if this is even the right way to go about this.
Can somebody point me in the right direction here?
Related
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
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>
I added a slick slider to Shopify's Boundless theme's product page and it properly displays the products images. The part where I'm having trouble is making the slider update to match the selected variant.
Here is the relevant JS:
productVariantCallback: function(variant) {
var $pageLink = $(selectors.pageLink, this.$container);
if (variant) {
if (variant.featured_image) {
var newImg = variant.featured_image;
var el = $(selectors.variantImage)[0];
var $slider=$('#product-slides');
if( $slider.length ) {
var newImgBase = newImg.src.substring(0, newImg.src.lastIndexOf('.')).split(':').pop();
if( $slider.hasClass('slick-initialized') ) {
var newIndex = $('#product-slides.slick-initialized img[src^="' + newImgBase + '"]')
.closest('.slick-slide')
.last()
.data('slick-index');
console.log( 'Goto slide ' + newIndex );
$slider.slickGoTo( newIndex, true );
} else {
var newIndex = $('#product-slides img').index( $('#product-slides img[src^="' + newImgBase + '"]'));
Shopify.sliderInitAt = newIndex ;
}
}
// more code, but I don't believe it is relevant to the issue
and the relevant chunks of liquid:
<div itemscope itemtype="http://schema.org/Product" data-section-id="{{ section.id }}" data-section-type="product" data-history-state>
{% assign current_variant = product.selected_or_first_available_variant %}
{% assign featured_image = current_variant.featured_image | default: product.featured_image %}
<meta itemprop="url" content="{{ shop.url }}{{ product.url }}">
<meta itemprop="image" content="{{ featured_image | img_url: 'grande' }}">
<meta itemprop="name" content="{{ product.title }}{% if product.variants.size > 1 and product.selected_variant %} - {{ current_variant.title }}{% endif %}">
<div class="page-width--wide" itemprop="offers" itemscope itemtype="http://schema.org/Offer" style="margin-top:20px;">
{% assign first_image = featured_image %}
{% if product.images.size > 1 and section.settings.skip_first_product_image and first_image == product.featured_image %}
{% assign first_image = product.images[1] %}
{% endif %}
{% if product.images.size > 0 %}
<div class="grid__item medium-up--three-fifths mobile-grid">
<div id="product-slides">
{% for image in product.images %}
<div>
<img class="product__photo--variant lazyload"
src="{{ image | img_url: 'master' }}"
data-src="{{ img_url }}"
data-widths="[360, 540, 720, 900, 1080, 1296, 1512, 1728, 1944, 2048, 4472]"
data-aspectratio="{{ image.aspect_ratio }}"
data-sizes="auto"
alt="{{ image.alt | escape }}">
</div>
{% endfor %}
</div>
<div class="slider-nav" style="margin-bottom:10px;">
{% for image in product.images %}
<div>
<img class="product__photo--variant lazyload"
src="{{ image | img_url: 'master' }}"
data-src="{{ img_url }}"
data-widths="[360, 540, 720, 900, 1080, 1296, 1512, 1728, 1944, 2048, 4472]"
data-aspectratio="{{ image.aspect_ratio }}"
data-sizes="auto"
alt="{{ image.alt | escape }}">
</div>
{% endfor %}
</div>
<noscript>
<img class="product__photo--variant" src="{{ first_image | img_url: '2048x2048' }}" alt="{{ image.alt | escape }}">
</noscript>
</div>
{% endif %}
<div class="grid__item medium-up--two-fifths mobile-grid">
<div class="grid grid--no-gutters product__details-content">
<div class="grid__item">
{% form 'product', product, id:form_id, class:form_class, data-cart-form: '' %}
<select name="id" id="ProductSelect-{{ section.id }}">
{% for variant in product.variants %}
<option {% if variant == product.selected_or_first_available_variant %} selected="selected" {% endif %} {% unless variant.available %} disabled="disabled" {% endunless %} value="{{ variant.id }}" data-sku="{{ variant.sku }}">{{ variant.title }} - {% if variant.available %}{{ variant.price | money_with_currency }}{% else %}{{ 'products.product.sold_out' | t }}{% endif %}</option>
{% endfor %}
</select>
{% endform %}
</div>
</div>
</div>
</div>
</div>
coding is not at all my field, so I have no idea what I'm saying when I say: I think it might be that my images need to be indexed on the liquid side then referenced on the JS side
<script>
$(document).ready(function(){
$('.imagesmain_1_sec').slick({
slidesToShow: 1,
slidesToScroll: 1,
arrows: false,
fade: true,
asNavFor: '.imagesmain_4_sec '
});
$('.imagesmain_4_sec').slick({
slidesToShow: 3,
slidesToScroll: 1,
vertical:true,
asNavFor: '.imagesmain_1_sec',
dots: false,
focusOnSelect: true,
});
});
</script>
I have a site with Drupal 8.9 and Twig Tweak.
I created 7 view blocks to set up a task list in stores. I want to display a task counter for my current user on my home page.
The tasks checks :
If the store has no product (danger).
If a product has no product variation (danger).
If an order does not have the processed status (danger).
If the store does not have a delivery method (danger).
If the store does not have a payment gateway (danger).
If a product from the store is not published (warning).
If the store owner does not have the "merchant" role (warning).
I created a display mode in the types of stores, which I rewrite (integrating the 7 task blocks) with the following code :
commerce-store--professionnel--tasks-frontpage.html.twig
{% set warnings = 0 %}
{% if drupal_view_result('boutique_page_liste_des_taches_produit_non_publie', 'block_1') is not empty %}
{% set warnings = warnings + 1 %}
{% endif %}
{% if drupal_view_result('boutique_page_liste_des_taches_role_marchand', 'block_1') is empty %}
{% set warnings = warnings + 1 %}
{% endif %}
{% set dangers = 0 %}
{% if drupal_view_result('boutique_page_liste_des_taches_aucun_produit', 'block_1', store_entity.id()) is empty %}
{% set dangers = dangers + 1 %}
{% endif %}
{% if drupal_view_result('boutique_page_liste_des_taches_aucune_variation', 'block_1', store_entity.id()) is not empty %}
{% set dangers = dangers + 1 %}
{% endif %}
{% if drupal_view_result('boutique_page_liste_des_taches_commande', 'block_1', store_entity.id()) is not empty %}
{% set dangers = dangers + 1 %}
{% endif %}
{% if drupal_view_result('boutique_page_liste_des_taches_mode_de_livraison', 'block_1', store_entity.id()) is empty %}
{% set dangers = dangers + 1 %}
{% endif %}
{% if drupal_view_result('boutique_page_liste_des_taches_passerelle_de_paiement', 'block_1', store_entity.id()) is empty %}
{% set dangers = dangers + 1 %}
{% endif %}
{% if dangers or warnings > 0 %}
<div class="alert alert-light border overflow-hidden shadow rounded hover mt-5 mb-0" role="alert">
<div>
<i class="fas fa-tasks fa-2x mr-4 float-left"></i>
<div class="alert-heading h5 mt-0 mb-2">Des tâches requièrent votre attention dans votre <span class="text-lowercase">{{ store_entity.type.entity.label }}</span> "{{ store_entity.name.value }}".</div>
<small>Veuillez passer en revue cette liste.</small>
</div>
<hr class="mt-3 mb-3 border">
<div class="d-flex justify-content-around">
{% if dangers > 0 %}
<p class="text-center mb-0"><i class="fas fa-times-circle fa-2x text-danger"></i><br>{{ dangers }} importante{% if dangers > 1 %}s{% endif %}</p>
{% endif %}
{% if warnings > 0 %}
<p class="text-center mb-0"><i class="fas fa-exclamation-circle fa-2x text-warning"></i><br>{{ warnings }} avertissement{% if warnings > 1 %}s{% endif %}</p>
{% endif %}
</div>
</div>
{% endif %}
{# /** TEST */ #}
{{ drupal_view('boutique_page_liste_des_taches_role_marchand', 'block_1', store_entity.id()) }}
I created a view block to display the stores of the current user :
https://i.stack.imgur.com/MpANw.png
I integrated this block to my home page :
page--front.html.twig
<div class="main-timeline">
{{ drupal_view('accueil_page_liste_des_taches_utilisateur', 'block_1') }}
{{ drupal_view('accueil_page_liste_des_taches_boutique', 'block_1') }}
{{ drupal_view('accueil_page_liste_des_taches_groupe', 'block_1') }}
{{ drupal_view('message_activity_stream_timeline_private', 'block_1') }}
</div>
I have disabled the cache for all views :
https://i.stack.imgur.com/JkhhN.png
Great, the counter works :
https://i.stack.imgur.com/QuQC2.png
My problem :
The counter never changes, to update it I have to do a drush cr.
How to correct this problem ?
Maybe I should add something to my .theme file.
To deactivate the cache of this template.
Or update this code with JS.
I DON'T WANT TO DESECTIVATE THE COVER FOR THE WHOLE SITE, BUT ONLY ON THE COUNTER.
Just for information, here is what the list of stains in a store looks like :
https://i.stack.imgur.com/xCqkx.png
UPDATE :
I added this code at the end of the commerce-store--professionnel--tasks-frontpage.html.twig template.
Only this block is updated without any problems. For the rest of the code, it is cache and there is no update without clearing the cache manually.
It seems that the code adds them to the code (the counting functionality) is problematic.
{# /** TEST */ #}
{{ drupal_view('boutique_page_liste_des_taches_role_marchand', 'block_1', store_entity.id()) }}
https://i.stack.imgur.com/Q146k.png
I did not clear the cache. The counter is not updated. The added block is updated.
https://i.stack.imgur.com/cW14w.png
(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];
}