I'm coding an app in Python Flask and I'm building interactive Like buttons with Javascript.
Here's my route:
#app.route('/jquery')
def jquery():
posts = Post.query.all()
return render_template('jquery.html', posts=posts)
And in the jquery.html template I have:
{% for p in posts %}
{% if p.upvotes %}
{% set pupvotes = p.upvotes %}
{% else %}
{% set pupvotes = 0 %}
{% endif %}
<p>{{ p.author.username }} says: <b>{{ p.body }}</b> <button id="
{{ p.id }}" onclick="document.getElementById('{{ p.id }}').innerHTML = {{
pupvotes }} +1 + ' Likes'">{{ pupvotes }} Likes</button></p>
{% endfor %}
Everything actually works this way, but I would like to save the results of +1 likes clicks and transfer them to a Python variable so that I can add it to the database and the updated Like numbers show up on the page after refresh.
I tried to use JavaScript function this way:
<script>
function myF1() {
document.getElementById('{{ p.id }}').innerHTML = {{ pupvotes }} +1 + '
Likes';
}
</script>
and:
onclick="myF1()"
But then only the last Like on the page gets updated with click, no matter which Like button I click.
OK, I've made it work:
Here's our Flask route:
#app.route('/ulk')
def ulk():
ppp = request.args.get('p', 0, type=int)
lpost = Post.query.filter_by(id=ppp).first()
lpost.upvotes += 1
db.session.commit()
return jsonify(result=ppp)
And here's our HTML page with some jQuery:
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
</head>
<body>
<script type=text/javascript>
$(function() {
$('a#like').bind('click', function() {
$.getJSON('/ulk', {
p: pid
}, function(data) {
$("#result2").text(data.result);
});
return false;
});
});
</script>
<br/><br/>
{% for p in posts %}
{% if p.upvotes %}
{% set pupvotes = p.upvotes %}
{% else %}
{% set pupvotes = 0 %}
{% endif %}
<p>{{ p.author.username }} says: <b>{{ p.body }}</b>
<a href="#" id=like>
<button id="{{ p.id }}" onclick="pid={{ p.id }};pvts = {{ pupvotes }} +1;myF1();">{{ pupvotes }} Likes</button>
</a>
</p>
<script type=text/javascript>
function myF1() {
$( "#" + pid ).text(pvts + ' Likes');
$('#' + pid).on('click', function() {
$(this).prop('disabled', true);
});
}
</script>
{%endfor%}
<br/>
<span id=result2>...</span>
</body>
</html>
You are close, however, as #MartijnPieters pointed out, you still need to communicate with the backend to update the number of likes for the post. To do so, slightly change your HTML to include a button to update the likes with a class and id. The id will be the same as the post id, and the class will be generic. Then, utilize jquery with ajax after creating a script.
First, in the Python backend, create a route to handle the updating of the likes for a post:
#app.route('/update_like')
def update_likes():
_id = int(flask.request.args.get('post_id'))
#Do something to update the database
return flask.jsonify({'success':'True'})
I suggest returning a jsonified response so that you can handle errors that may occur, such as a user liking a post twice. If you discover that that is the case, then you could return flask.jsonify({'success':'False'}) and handle that accordingly in the frontend.
Then, in the HTML:
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
</head>
<div class='post_wrapper'>
{%for p in posts%}
<div id='post_{{post.id}}'>
<p>{{p.author.username}} says:</p>
<p>{{p.body}}</p>
{%if p.upvotes%}
<button class='upvote' id='upvote_{{p.id}}'>Like <span id='upvotes_{{p.id}}'>{{p.upvotes}}</span></p>
{%else%}
<button class='upvote' id='upvote_{{p.id}}'>Like <span id='upvotes_{{p.id}}'>0</span></p>
{%endif%}
</div>
{%endfor%}
</div>
<script>
$(document).ready(function(){
$('.post_wrapper').on('click', '.upvote', function(){
var post_id = this.id.match('\\d+');
$.ajax({
url: "/update_like",
type: "get",
data: {post_id: post_id},
success: function(response) {
if (response.success === 'True'){
var like_val = parseInt($('#upvotes_'+post_id).text()) + 1;
$('#upvotes_'+post_id).text(like_val.toString());
}
else{
alert('You already liked that post!');
}
}
});
});
});
</script>
</html>
Related
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 was wondering if there is a way i can change the body of for loop in django using javascript each time i press a button.
in my case i want to display matches this week and when i press next i want to change the list using javascript and then pass it django template in the regroup part, i want to change the matches list.
i know how to write the code to make the new list and the previous and next buttons using javascript but i don't know how to pass it to django template
or maybe another way could be to write django code in javascript, anyone can help with either way?
in views.py , matches return a list of dictionaries from today to 6 days later
def home(request):
start = datetime.now().date()
end = today + timedelta(6)
matches = request_games(today, after_week)
return render(request, "sporty/home.html",{
"matches": matches,
"start" : start,
"end": end
})
in home.html
{% extends "sporty/layout.html" %}
{% load static %}
{% block body %}
<div class="box">
{{start}},{{end}}
{% regroup matches by date as date_list %}
{% for date in date_list %}
<div class="the_date">
{{date.grouper}}
</div>
{% for match in date.list %}
<div class="match_container">
<div class="status">
{% if match.status_code == 1%}
{{match.minute}}'
{% elif match.status_code == 11%}
HT
{% elif match.status_code == 3 %}
Finished
{% endif %}
</div>
<div class="match">
<div class="home">
{{match.home_name}} <img src="{{match.home_logo}}">
</div>
<div class="score">
{% if match.status_code == 0 %}
{{match.time}}
{% elif match.status_code == 17 %}
TBD
{% elif match.status_code == 1%}
{{match.home_score}} : {{match.away_score}}
{% elif match.staus_code == 11%}
{{match.home_score}} : {{match.away_score}}
{% elif match.status_code == 3 %}
{{match.home_score}} : {{match.away_score}}
{% endif %}
</div>
<div class="away">
<img src="{{match.away_logo}}">
{{match.away_name}}
</div>
</div>
</div>
{% endfor %}
{% endfor %}
</div>
{% endblock %}
def request_games(start, end):
params = (
("season_id","1511"),
("date_from",start.strftime("%Y-%m-%d")),
("date_to",end.strftime("%Y-%m-%d"))
);
headers = {
"apikey": //my api key
}
response = requests.get('https://app.sportdataapi.com/api/v1/soccer/matches', headers=headers, params=params)
r = response.json()
data = r["data"]
number = len(data)
matches = []
for i in range(number):
match = {}
match['status'] = data[i]["status"]
match['status_code'] = data[i]["status_code"]
match['minute'] = data[i]['minute']
full_date = data[i]["match_start"]
dt = datetime.strptime(full_date, '%Y-%m-%d %H:%M:%S')
match['date'] = dt.date()
match['time'] = dt.time()
match['start'] = full_date
home_team = data[i]["home_team"]
match['home_name'] = home_team['name']
match['home_logo'] = home_team['logo']
away_team = data[i]["away_team"]
match['away_name'] = away_team['name']
match['away_logo'] = away_team['logo']
stats = data[i]['stats']
match['home_score'] = stats['home_score']
match['away_score'] = stats['away_score']
matches.append(match)
matches.sort(key = lambda x: datetime.strptime(x['start'], '%Y-%m-%d %H:%M:%S'))
return matches
First thing first, once you decide to start working on pages that do not refresh but still query the server and change, one of your solutions is to start using AJAX calls to query the server for different data.
This would add a bit of code to your project.
For example, your home.html template will need to be split into two. Make sure to also add jQuery to your head.
sporty/home.html
{% extends "sporty/layout.html" %}
{% load static %}
{% block body %}
<div id="matchweek" class="box">
{% include "sporty/matchweek.html" %}
</div>
<button type="button" value="p" onclick="weekControl(this)">Previous</button>
<button type="button" value="n" onclick="weekControl(this)">Next</button>
{% comment %}
Either place weekupdate.js in /static/ or give the path within static,
best would be to have a folder for javascript in static and use 'js/weekupdate.js'
{% endcomment %}
<script src="{% static 'js/weekupdate.js' %}"></script>
{% endblock %}
and sporty/matchweek.html
{{start}},{{end}}
{% regroup matches by date as date_list %}
{% for date in date_list %}
<div class="the_date">
{{date.grouper}}
</div>
{% for match in date.list %}
<div class="match_container">
<div class="status">
{% if match.status_code == 1%}
{{match.minute}}'
{% elif match.status_code == 11%}
HT
{% elif match.status_code == 3 %}
Finished
{% endif %}
</div>
<div class="match">
<div class="home">
{{match.home_name}} <img src="{{match.home_logo}}">
</div>
<div class="score">
{% if match.status_code == 0 %}
{{match.time}}
{% elif match.status_code == 17 %}
TBD
{% elif match.status_code == 1%}
{{match.home_score}} : {{match.away_score}}
{% elif match.staus_code == 11%}
{{match.home_score}} : {{match.away_score}}
{% elif match.status_code == 3 %}
{{match.home_score}} : {{match.away_score}}
{% endif %}
</div>
<div class="away">
<img src="{{match.away_logo}}">
{{match.away_name}}
</div>
</div>
</div>
{% endfor %}
{% endfor %}
Because you will need to update the entire content of matchweek div every time.
views.py
def home(request):
if request.is_ajax():
template = 'matchweek.html'
direction = request.GET.get('dir')
if direction == 'n':
request.session['weekoffset'] += 1
elif direction == 'p':
request.session['weekoffset'] -= 1
else:
template = 'home.html'
request.session['weekoffset'] = 0
offset = request.session['weekoffset']
start = datetime.now().date() + timedelta(days=7*offset)
end = start + timedelta(days=6)
matches = request_games(start, end)
return render(request, f"sporty/{template}",{
"matches": matches,
"start": start,
"end": end
})
What you see in the function is a check if the request is coming from the AJAX call or not and determination on what to do from there.
/static/js/weekupdate.js
function weekControl(id) {
var value = id.value;
$.ajax({
url: '', // The url suffix that leads to your home function, example: '/home/'
type: "GET", // Http method
data: {'dir': value}, // The data to be sent to the server.
success: function (htmlres) { // What to do on success and response reaching back
$("#matchweek").html(htmlres);
}
});
}
What you see here is the AJAX get call to your django view with the data of 'dir' and its value. When it is returned, jQuery will change the content of the matchweek div with the new content.
I haven't tested this but let me know if this works and if you have any questions.
results.html
{%if searched_user %}
{{searched_user}}
<button id=likedsongsbutton>View liked songs</button>
<div>
{% for each_searched_user in searched_user %}
<br id="likedsongs"/>{% for liked_songs in each_searched_user.liked_songs.all %}{{liked_songs}}
<br/>
{% endfor %}
{% endfor %}
{% endif %}
{% endblock %}
</div>
<script>
document.querySelector("#likedsongsbutton").addEventListener(onclick, )
</script>
views.py
def results(request):
if request.method == "GET":
search_query = request.GET.get("username")
searched_user = UserProfile.objects.filter(
user__username__contains=search_query
)
return render(
request, "results.html", {
"searched_user":searched_user
})
My question is how do I make the <div> show only when the button is clicked? I do not know what function to pass in the EventListener
If you give your change you div to have an id of myDiv and set the display to be None, you can pass the following lambda in as the second parameter.
() => {document.getElementById("myDiv").style.display = ""}
While the div looks like:
<div id="myDiv" style="display: none"></div>
I also think you should change it from onclick, to "click". Here is a complete example:
<button id=likedsongsbutton>View liked songs</button>
<div id="myDiv" style="display: none; background: blue; height: 100px; width: 100px;"></div>
<script>
document.querySelector("#likedsongsbutton").addEventListener("click", () => {
document.getElementById("myDiv").style.display = ""
});
</script>
I've got a paginated page on shopify that I'm trying to get a infinite scroll working using javascript/ ajax.
The liquid looks something like this:
{% paginate collection.products by 20 %}
<!-- START PRODUCTS -->
{% for product in collection.products %}
<!-- START PRODUCT {{ forloop.index | plus:paginate.current_offset }} -->
<div class="product" id="product-{{ forloop.index | plus:paginate.current_offset }}">
{% include 'product' with product %}
</div>
<!-- END PRODUCT {{ forloop.index | plus:paginate.current_offset }} -->
{% endfor %}
{% if paginate.next %}
<div id="more"><p>↓ More</p></div>
{% endif %}
<div id="product-list-foot"></div>
<!-- END PRODUCTS -->
<!-- the bottom of your collections.liquid -->
{% endpaginate %}
And the JS:
<script>
function ScrollExecute() {
if($(document).height() - 100 < ($(document).scrollTop() + $(window).height())) {
scrollNode = $('#more').last();
scrollURL = $('#more p a').last().attr("href");
if(scrollNode.length > 0 && scrollNode.css('display') != 'none') {
$.ajax({
type: 'GET',
url: scrollURL,
beforeSend: function() {
scrollNode.clone().empty().insertAfter(scrollNode).append('<img class="loading_gif" src=\"{{ "ajax-loader.gif" | asset_url }}\" />');
scrollNode.hide();
},
success: function(data) {
// remove loading feedback
scrollNode.next().remove();
var filteredData = $(data).find(".product");
filteredData.insertBefore( $("#product-list-foot") );
},
dataType: "html"
});
}
}
}
$(document).ready(function () {
$(window).scroll(function(){
$.doTimeout( 'scroll', 100, ScrollExecute);
});
});
</script>
This works great but with one issue. The first paginated page works fine, but if there's 3 pages in total for example it doesn't load the third page. So the load function only works once.
Any ideas why this only works once?
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 %}