I'm taking the CS50 course "Web Programming with Python and Javascript".
In one of the projects that need to be done, one have to implement a social network platform where users can post some texts, like and dislike them and follow other users.
There are 3 routes a user can go:
All posts,
Following and
users posts.
I've been trying to solve this with js front end. I've a function "view_posts", that takes an argument that specifies what posts should be fetched and rendered to the html page. Consider the following part of my js file:
document.addEventListener('DOMContentLoaded', function() {
username = document.getElementById('username').innerText
document.querySelector('#posts').addEventListener('click', () => view_posts('all'));
document.querySelector('#username').addEventListener('click', () => view_posts(username));
document.querySelector('#following').addEventListener('click', () => view_posts('followers'));
document.querySelector('#post-form').style.display = "none";
view_posts('all')
});
Fetching the data is basically working, but js is always displaying the default view "view_posts('all')". Meaning, even if I click on the element whose id is #post, calling the very same function including the same argument, the function is called without displaying anything, before it calls the default view again (this time it will display the data correct).
function view_posts(selector) {
// document.querySelector('#post-form').style.display = "none";
if (selector === 'all') {
document.querySelector('#header').innerText = "All Posts";
document.querySelector('#post-form').style.display = "block";
} else if (selector == 'following') {
document.querySelector('#header').innerText = "Following";
} else {
document.querySelector('#header').innerText = selector;
}
fetch(`/network/${selector}`)
// .then(response => response.text())
// .then(posts => console.log(posts))
.then(response => response.json())
.then(posts => {
posts.forEach((element) => {
var item = document.createElement('div');
item.className = "card";
item.innerHTML = `<div class="card-body" id="item-${element.id}">
${element.author} | ${element.timestamp}
<br>
${element.body}
</div>`;
document.querySelector('#post-view').append(item);
// document.getElementById('item-${element.id}').addEventListener('click', () => view_posts(element.author))
});
});
// .catch(console.log.bind(console));
};
EDIT: here is my index.html
{% extends "network/layout.html" %}
{% load static %}
{% block body %}
<div id="post-view">
<h2 id="header" style="margin:10px"></h2>
<div id=post-form>
<h4>New Post</h4>
<form>
<textarea class="form-control" id="compose-body" placeholder="Type here..."></textarea>
<input onclick="create_post()" type="button" class="btn btn-primary" value="Post"/>
</form>
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static 'network/index.js' %}"></script>
{% endblock %}
To not append
document.getElementById('post-view').innerHTML = posts.map(element => (
`<div class="card">
<div class="card-body" id="item-${element.id}">
${element.author} | ${element.timestamp}<br>${element.body}
</div>
</div>`).join("");
Related
i would like to reuse parts of the url in the content of the webpage
as example
domain.com/specialist/brand/location/
where i want to reuse "brand" multiple times in the content of the webpage
and also "location" multiple times in the content of the webpage.
first i tried it with variables
url/?specialist=brand&bezorgen=city
but this does not work with the following method, because it renders them only once
i've tried the
<script>
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const specialist = urlParams.get('specialist');
const bezorgen = urlParams.get('bezorgen');
document.getElementById("specialist").innerHtml = specialist;
document.getElementById("bezorgen").innerHtml = bezorgen;
</script>
and in the html
<span id=specialist></span>
<span id=bezorgen></span>
What would be the fastest way to get this working
technology = twig and js
Thanks in advance
Twig
As you are using a custom framework I'd suggest you to mimick the app.request.get behavior that is available in Symfony.
First create a class that delivers the same logic
Request.php
class Request {
public function __construct() {}
public function get($key) {
return isset($_GET[$key]) ? $_GET[$key] : null;
}
public function post($key) {
return isset($_POST[$key]) ? $_POST[$key] : null;
}
public function url() {
$http = 'http'.(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 's': '');
return $http.'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
}
}
Register the class in twig
The easiest way to add an instance of the Request class is just to add it as a global
<?php
...
$twig->addGlobal('app', [ 'request' => new Request(), ]);
Access the class inside a template
<p>this {{ app.request.get('brand') }} bike is pretty awesome and we can deliver this at {{ app.request.get('location') }}. This {{ app.request.get('brand') }} is really trustworthy</p>
mandatory note
Please be aware it is possible that clients will try to inject/generate unsafe output. However twig will prevent this as long as you don't mark the content as safe with e.g. the filter raw
Javascript
The problem with your javascript is probably because you are creating elements with the same id over and over again, which is why only the first element with the correct id will be replaced correctly
An easy solution would be to switch to classes
var foo = 'foo';
var bar = 'bar';
document.querySelectorAll('.foo').forEach(span => {
span.innerHTML = foo;
});
document.querySelectorAll('.bar').forEach(span => {
span.innerHTML = bar;
});
span {
display: block;
}
span + span {
margin: 10px 0 0 0;
}
span.foo {
color: red;
}
span.bar {
color: green;
}
<span class="foo"></span>
<span class="foo"></span>
<span class="bar"></span>
<span class="foo"></span>
<span class="foo"></span>
<span class="bar"></span>
I might be misunderstanding, but the OP seems to ask two things: (1) how to extract info from the url -- either from within the path or the query, (2) how to alter the dom with that info.
Looking at (1), you can get a lot of information from the expression new URL(window.location.href);
// using the path
// these window-named vars to simulate the browsers' window global
let windowA = {
location: {
href: "http://example.com/specialist/mybrand/mylocation"
}
};
const urlA = new URL(windowA.location.href);
console.log(urlA.pathname.split('/').slice(-2));
// using the query
let windowB = {
location: {
href: "http://example.com/specialist/?brand=mybrand&location=mylocation"
}
};
const urlB = new URL(windowB.location.href);
const params = [urlB.searchParams.get('brand'), urlB.searchParams.get('location')]
console.log(params);
Looking at (2), the OP code looks fine, except the div's ids need to be placed in quotes...
let _window = {
location: {
href: "http://example.com/specialist/mybrand/mylocation"
}
};
const url = new URL(_window.location.href);
const components = url.pathname.split('/').slice(-2);
document.getElementById("brand").innerHTML = `<h1>${components[0]}</h1>`;
document.getElementById("location").innerHTML = `<h1>${components[1]}</h1>`;
<!-- notice the quoted id expressions -->
<div id="brand"></div>
<div id="location"></div>
twig only solution:
https://www.url.com/service/bikespecialist/trek/amsterdam/
{% set brand %}{{ request.url| split('/', 7)[5]| trim('/') }}{% endset %}
{% set location %}{{ request.url| split('/', 7)[6]| trim('/') }}{% endset %}
in the text
this {{ brand }} bike is awesome, come and buy it at {{ brand }} {{ location }}
To all who replied, many thanks! this problem had me bugged for the last couple weeks
and in the case of no brand or location in the url
{% set band %}{{ request.url| split('/', 7)[5]| trim('/') |capitalize }}{% endset %}
{% if brand is empty %}
{% set brand %}qualitybikes {% endset %}
{% endif %}
{% set loation %}{{ request.url| split('/', 7)[6]| trim('/') |capitalize }}{% endset %}
{% if location is empty %}
{% set location %}entire country {% endset %}
{% endif %}
I am new to JavaScript. I'm working on a project in which I'm using Django in the backend. My Django views function will produce a JSON response which my javascript fetch will get it. Here is my Django views function that produces a JSON response. My motive is to make a like button that will update the like button's appearance and the number of likes by clicking on that button without reloading the whole page. This is the last requirement of my project. I am trying for a long time, a couple of days.
def likepost(request,posts_id):
posts = NewPost.objects.get(id = posts_id)
is_like = False
for like in posts.likepost.all():
if like == request.user and request.method == "POST":
is_like = True
break
if not is_like:
posts.likepost.add(request.user)
else:
posts.likepost.remove(request.user)
posts.save()
# serialize_obj = serializers.serialize("json",posts_id)
return JsonResponse({
"is_like" : is_like,
"num_like" : posts.likepost.count()
},safe=False)
My javascript will make an API of the JSON data generated the above views function using fetch. Here is my javascript full code.
document.addEventListener("DOMContentLoaded",function(e){
// const colon = document.createElement('div');
// colon.setAttribute('id','colon')
e.preventDefault()
// const likebtn = document.createElement('button');
// likebtn.setAttribute('class','likebtn btn btn-primary');
// likebtn.setAttribute('class','likebtn');
// document.querySelector('.card-footer').appendChild(likebtn);
// document.querySelector('.likebtn').innerHTML = "Like";
document.querySelector(`#likebtn${posts_id}`).onsubmit = like_function();
// document.querySelector('.likepost').addEventListener('click', ()=> like_function('likepost'));
})
// let is_like = "{{is_like}}";
// let num_like = "{{num_like}}";
function like_function(){
// document.createElement('button').innerHTML = "Love";
fetch(`/like/${posts_id}`)
// ,{
// method:"POST",
// body : JSON.stringify({
// "is_like" : is_like,
// "num_like" : num_like,
// })
// })
.then(response => response.json())
.then(result => {
if(result.is_like){
document.querySelector(`#likebtn${posts_id}`).innerHTML = "Unike";
// location.replace("http://127.0.0.1:8000")
}
else{
document.querySelector(`#likebtn${posts_id}`).innerHTML = "Like";
// location.replace("http://127.0.0.1:8000")
}
})
}
// function like_function(){
// if (document.querySelector("#like").style.color == "blue"){
// document.querySelector("#like").style.color = "red";
// }else{
// document.querySelector("#like").style.color = "blue";
// }
// }
Here is my urls.py file.
urlpatterns = [
path("", views.index, name="index"),
path("login", views.login_view, name="login"),
path("logout", views.logout_view, name="logout"),
path("register", views.register, name="register"),
path("profile/<int:id>",views.profilepage,name="profile"),
path("profile/<int:id>/following/addfollower",views.followersPeople,name="addfollower"),
path("profile/<int:id>/following/removefollower",views.followersRemove,name="removefollower),
path("postform", views.createpost, name="postform"),
path("editform/<int:id>",views.editpost,name="editpost"),
path("following",views.followerspost,name="following"),
path("like/<int:posts_id>",views.likepost, name="likepost"),
path("postpage/<int:id>",views.view_post,name="postpage"),
]+ static(settings.MEDIA_URL, document_root= settings.MEDIA_ROOT)
I am also sharing my template here.
{% load static %}
<div class="card-footer">
<form action="{% url 'likepost' posts_id=posts.id %}" class="likeform" method="POST" style="display: inline;">
{% csrf_token %}
<button id="likebtn{{posts.id}}" class="btn btn-link" type="submit">Like</button>
</form>
<small class="num_of_likes">{{ posts.likepost.all.count }}</small>
{% block script %}
<script>
let posts_id = "{{ posts.id }}";
</script>
<script src="{% static 'network/controller.js' %}"></script>
{% endblock %}
<button class="btn btn-link" style="text-decoration: none;">Comment</button>
View Post
{% if request.user.id is posts.user.id %}
Edit
{% endif %}
<div class="likepost"></div>
</div>
My project doesn't work well. When I click on the like button it appears like this
instead of updating the like button into the unlike button and the number of likes as well as the page shouldn't be reloaded. What should I do? Please let me know if I need to share more pieces of code although I see my code is very much messier.
In your video it looks like the form is being sent instead of preventing the POST default.
document.querySelector(`.likeform`).onsubmit = () => like_function();
Look into this answer for more detail about .onsubmit
I am building an integration with Stripe by following the examples in its documentation, but I can't understand the part of creating a Charge for more than one product.
I was looking all over Stripe's docs and was searching for any articles / forums about the similar issue but was unable to find anything. I'd be very grateful to either some links to the articles on this matter or any tips to help me to understand how to solve it.
Here's a server side code:
```python
#app.route("/checkout", methods=["GET", "POST"])
def checkout():
if request.method == "POST":
# Process a JSON string with a checkout information:
# { item_id: item_quantity, ... }
# Build the SQL query based on it
items = {}
shopping_cart = request.form["cart_checkout"]
shopping_cart = shopping_cart.lstrip("{")
shopping_cart = shopping_cart.rstrip("}")
shopping_cart = shopping_cart.split(",")
sqlQuery = "SELECT * FROM Items WHERE item_id IN ("
for KeyValPair in shopping_cart:
Key = KeyValPair.split(":")[0]
Key = Key.strip('"')
sqlQuery = sqlQuery + Key + ","
Value = KeyValPair.split(":")[1]
items[Key] = Value
sqlQuery = sqlQuery.rstrip(",")
sqlQuery = sqlQuery + ") ORDER BY item_id ASC"
cart_items = sql_select(sqlQuery)
# Add a column about the quantity of items
for item in cart_items:
item["quantity"] = items[item["item_id"]]
# Build a Stripe checkout list
line_items_list = []
for item in cart_items:
line_item = {}
line_item["name"] = item["item_name"]
line_item["description"] = item["item_description"]
line_item["amount"] = item["price"]
line_item["currency"] = "usd"
line_item["quantity"] = item["quantity"]
line_items_list.append(dict(line_item))
stripe_session = stripe.checkout.Session.create(
submit_type="pay",
payment_method_types=["card"],
line_items=line_items_list,
success_url='https://example.com/success',
cancel_url='https://example.com/cancel',
)
return render_template("checkout.html",
stripe_id=stripe_session.id,
stripe_pk=stripe_keys["PUBLIC_KEY"])
return redirect("/")
```
And here's a part of HTML template:
```html
<form action="/checkout" method="post" id="form_checkout" onsubmit="return cart_info()"
...
<input type="hidden" name="cart_checkout" id="checkout_info" value="{{ cart_checkout }}">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_pk }}"
data-name="Company Name"
data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
data-description="A description of the product or service being purchased"
data-amount="999"
data-shipping-address="true"
data-zip-code="true"
data-allow-remember-me="true"
data-panel-label="Pay"
data-label="Checkout"
data-locale="auto">
</script>
</form>
```
If I do a simple example of Charge from Stripe docs, like this:
```python
#app.route('/charge', methods=['POST'])
def charge():
# Amount in cents
amount = 500
customer = stripe.Customer.create(
email='customer#example.com',
source=request.form['stripeToken']
)
charge = stripe.Charge.create(
customer=customer.id,
amount=amount,
currency='usd',
description='Flask Charge'
)
return render_template('charge.html', amount=amount)
```
Then I can create without any issues a successful test Charge, it displays with the success label in my Stripe's dashboard. If I use stripe.checkout.Session.create, Stripe dashboard properly creates an incomplete record about my Checkout session with the selected list of items, but I've no idea how to proceed from there to finalise the Charge for them.
As often happens, when I start asking questions, I eventually find the answers on my own, lol. I've had a "checkout.html" template but it didn't work, and no errors were displaying, so I assumed that I was missing some more code required for it all to work.
As it happened, all I was missing, was "" in a line of code. Here's a working Checkout session with the addition of a bit of JavaScript:
{% extends "layout.html" %}
{% block title %}Checkout{% endblock %}
{% block head %}
<script src="https://js.stripe.com/v3/"></script>
{% endblock %}
{% block main %}
<!-- Main content -->
<div class="wrapper main-content">
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endwith %}
<h2 class="section-header">Checkout</h2>
<p id="result_msg"></p>
<button onclick="checkout()">Checkout</button>
</div>
<script type="text/javascript">
function checkout() {
var stripe = Stripe("{{ stripe_pk }}");
stripe.redirectToCheckout({
// Make the id field from the Checkout Session creation API response
// available to this file, so you can provide it as parameter here
// instead of the {{CHECKOUT_SESSION_ID}} placeholder.
sessionId: "{{CHECKOUT_SESSION_ID}}"
}).then(function (result) {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer
// using `result.error.message`.
document.getElementById("result_msg").innerHTML = result.error.message;
});
}
</script>
{% endblock %}
I have a page called controlpanel, where I can start/stop scripts. When the button for start and stop scripts is pressed, it return the same page with button color changed and I want to delete query string.
view.py
def controlpanel(request):
data = {'paypal': paypal_, 'cpu': cpu_usage, 'active': active_task ...}
return render(request, 'admin/controlpanel.html', context=data)
def startapp(request):
if request.META['QUERY_STRING']:
#... start scripts etc..
request.META['QUERY_STRING'] = False
return controlpanel(request)
the function return controlpanel with query string... (127.0.0.1:8000/startapp?run=True but I only want 127.0.0.1:8000/controlpanel)
controlpanel.html
<div>
{% if active %}
<button onclick="f()" class="btn btn-md btn-success">Start</button>
{% else %}
<button onclick="f()" class="btn btn-md btn-warning">Start</button>
{% endif %}
</div>
<script>
function f() {
let a = null;
if ('{{active}}' === 'True') {
a = 'stop'
} else {
a = 'start'
}
window.location.href = "/startapp?run=" + a;
}
</script>
You could use Django's redirect to return the URL without the query parameters.
I've had a simple setup displaying a variable using ng-bind-html. But now requisites have changed and I part to display comes from a collection and needs to display a pager.
So I thought about using a functionI , thinking that it would be easy, but failing miserably at it.
The expected workflow:
Via AJAX I load the content of a select field (working)
When the user selects one of the options, the function should get called to paint the content from the collection that is assigned to the selected option, including a pager to navigate within the collection (not working).
What I have so far:
{% extends 'BDAMainBundle::layout.html.twig' %}
{% block title %}Kurs-Auswahl{% endblock %}
{% block body %}
<div data-ng-controller="SingleCourseDownloadCtrl">
<div class="row">
<div class="col-xs-8">
{{ textSnippet('snippet.solo_download_selection')|raw }}
<div data-ng-show="loading">
<span class="inline-ajax-indicator"></span>
</div>
<div data-ng-hide="loading || courses.length == 0" class="input-group">
<select
class="form-control"
data-ng-change="tcAccepted = false"
data-ng-model="course"
data-ng-options="c|courseSelectionCourseLabel for c in courses"
><option value="">Bitte wählen:</option></select>
....
<div data-ng-show="course">
<div>
{% verbatim %}
{{ paged_weekly_exercises }}
{% endverbatim %}
</div>
</div>
</div>
....
{% endblock %}
and:
app.controller('SingleCourseDownloadCtrl', ['$scope', '$window', 'CourseManager', 'RemoteRouteConfig', function ($scope, $window, CourseManager, RemoteRouteConfig) {
$scope.courses = [];
$scope.loading = true;
$scope.paged_weekly_exercise;
$scope.course = [];
// load
CourseManager.getAvailableForSoloDownload().then(function (response) {
$scope.courses = response;
$scope.loading = false;
$scope.$watch('course', function(){
if(!course.length){
return false;
}
$scope.paged_weekly_exercise = $scope.course.weekly_exercises[0].content;
});
});
// starts the given course for single download
$scope.startCourse = function startCourse(course) {
$window.location = RemoteRouteConfig.course.startSoloDownload({
course: course.id
});
};
}]);
But I feel I went a totally wrong path. How should I go to accomplish my goal?
I have minimal experience with Angular, but am quite decent at Javascipt and JQuery.
Instead of adding a watcher in your controller, just use the ng-change:
<select
class="form-control"
data-ng-change="courseChanged()"
data-ng-model="course"
data-ng-options="c|courseSelectionCourseLabel for c in courses"
In your controller:
$scope.courseChanged = function() {
$scope.tcAccepted = false;
var course = $scope.course; // the used ng-model
if(!course.length){
return;
}
$scope.paged_weekly_exercise = course.weekly_exercises[0].content;
}