I want to implement the apostrophes Ajax features in my project but sadly I don't understand the Ajax functions not completely. I have been studying the documentation for the last week now but i don't find enough information about that topic. I want kindly ask if it's possible to use data-apos-ajax-append outside of apostrophe-pieces-pages
I created a widget like that:
lib/modules/infinite-widgets/index.js
module.exports = {
extend: 'apostrophe-widgets',
label: 'Infinite Widget',
addFields: [
{
name: 'posts',
label: 'Posts',
type: 'array',
titleField: 'name',
schema: [
{
name: 'name',
type: 'string',
label: 'Name'
},
{
name: '_image',
type: 'joinByOne',
withType: 'apostrophe-image',
label: 'Image',
required: true,
filters: {
projection: {
attachment: 1,
description: 1,
title: 1
}
}
}
]
}
]
};
lib/modules/infinite-widgets/views/widget.html
<div class="row margin">
<div class="col s12 m12">
<div class="radius
{% if data.widget.shadowOn == true %}
z-depth-1
{% endif %}
">
<div data-apos-ajax-context="infinite">
{% include "indexAjax.html" %}
</div>
</div>
</div>
</div>
lib/modules/infinite-widgets/views/indexAjax.html
<div data-apos-ajax-append>
{% for name in data.widget.posts %}
{% set image = apos.images.first(name._image) %}
<div class="card z-depth-0" style="
{%- if piece.backColor-%}
background-color:{{ piece.backColor }}
{% endif %}
">
<div class="card-image">
<img src="{{ apos.attachments.url(image, { size: 'one-sixth' }) }}" />
</div>
<div class="card-content">
{{ name.name }}
</div>
</div>
{% endfor %}
</div>
That is working pretty nice but unfortuantly It displays all posts created simultansiosly and I wasen't able to use the "Load More..." button with AJAX here
{# Load More button. Also outside data-apos-ajax-append, so it gets refreshed #}
{% if data.currentPage < data.totalPages %}
{# "Load More" button with the "append=1" flag #}
Load More...
{% endif %}
So can I modify this query, to have a load more button, or even better data-apos-ajax-infinite-scroll for content of an array created in an widget instead of an piece?
No, it is not possible to do that using data-apos-ajax-append. That feature is specifically for apostrophe-pieces-pages and there is no back end implementation waiting to talk to it for widgets, for instance.
You should instead look at writing a browser-side widget player for your widget, which gives you a place to make your own API calls back to the server. You can see that technique in the source of the apostrophe-twitter-widgets module.
Related
I am working on a small Flask app and the user is able to update a list of items via using a form to submit some data.
I am currently using SQL Alchemy as the ORM for my database and using the list in my template to display the items. I want to update it so that when the list is updated. The page is updated without the user having to reload the page.
I have tried this with AJAX using the below script but the update is not occurring.
$(document).ready(function(){
$('form').on('submit',function (e) {
$.ajax({
type: 'post',
url: '/todo',
data: $('#todoInput').serialize(),
success: function (q) {
console.log(q);
}
});
e.preventDefault();
});
}
My template:
{% extends "base.html" %}
{% block script %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="{{ url_for('static', filename='js/custom.js') }}"></script>
{% endblock %}
{% block content %}
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-success" role="alert">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="row justify-content-md-center">
<div class="col-md-auto">
<h1>What needs to be done today?</h1>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-auto">
<form action="" method="post">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.todo (id="todoInput") }}
{{ form.submit (class_="btn btn-primary btn-sm") }}
</div>
</form>
<div id="todoList">
<ul class="list-group">
{% for todo in todos %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ todo.body }}
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %}
Model:
from datetime import datetime
from app import db, login
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
todos = db.relationship('Todo', backref='owner', lazy='dynamic')
def __repr__(self):
return '<User {}>'.format(self.username)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
#login.user_loader
def load_user(id):
return User.query.get(int(id))
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Todo {}>'.format(self.body)
Route:
#app.route('/todo', methods=['GET', 'POST'])
def todo():
if current_user.is_authenticated:
form = TodoForm()
todos = current_user.todos.order_by(Todo.timestamp.desc()).all()
if form.validate_on_submit():
new_todo = Todo(body=form.todo.data, owner=current_user)
db.session.add(new_todo)
db.session.commit()
flash('Todo added')
todos = current_user.todos.order_by(Todo.timestamp.desc()).all()
return render_template('todo.html', form=form, todos=todos)
return render_template('todo.html', form=form, todos=todos)
flash('You first need to login or register')
return redirect(url_for('index'))
The todos list is what im trying to update. But it just reloads the page :/
You need to do two things. Firstly you need to be sure that your database is updated. That means that your form was submitted successfully. This can be done by submitting the form manually via an ajax call. This is more or less what you did. Did you check, whether your backend receives the form-submission and updates the database?
Secondly you need to update the html in the browser. Normally the page is refreshed automatically if your form is submitted. If you dont want a page reload, you need to add some client-side logic, to ensure that the page is updated. This can easily be done with a little bit of javascript.
Edit:
Your custom js might be missing }); at the end. Or did you just forget to post it here?
The Problem
I have a div on my Jekyll site where I would like to display data that is generated from _data/book-data.yml, for example:
- name: johnson-everything-under
title: Everything Under
author: Daisy Johnson
publisher: Faber & Faber
pub-date: 12/12/2012
- name: johnson-train-dreams
title: Train Dreams
author: Denis Johsnon
publisher: Granta
pub-date: 01/01/2001
I'm trying to find a way to display the data that corresponds to the relevant page (each book entry has a different page), and I thought maybe this would be possible if the name key corresponds to the page.url, or some other page variable.
What I've tried
On a working page, I have an include which contains the following HTML:
<div class="book-meta">
{% if page.category == "reviews"%}
<div class="book-thumbnail">
<img class="post-thumbnail-lg" src="{{ site.baseurl }}/assets/images/{{ page.thumbnail }}" alt="{{ page.thumbnail }}">
</div>
{% for book in site.data.book-data %}
<div class="book-details">
<ul class="book-meta-list">
<li class="book-meta-item"><p>{{ book.author }}</p></li>
<li class="book-meta-item"><p><em>{{ book.title }}</em></p></li>
<li class="book-meta-item"><p>{{ book.publisher }}</p></li>
<li class="book-meta-item"><p>{{ book.pub-date }}</p></li>
</ul>
</div>
{% endfor %}
{% endif %}
</div>
The CSS is not important, but as it is currently the output of the above HTML is:
Desired output
As you can see, the output contains both the blocks of metadata from the .yml file. I would like to find a way so that only the relevant block of data (the first block in this instance) is displayed:
Potential solution(s)
I thought that there might be a way of matching a page.variable to the YAML block name so only the right book data gets output. Something along the lines of:
{% assign url_substring = page.url | split, '/' | last %}
// url_substring = johnson-everything-under
{% for book in site.data.book-data %}
{% if url_substring == book.name %}
// = true
<p>{{ book.title }}<p>
{% endif %}
{% endfor %}
Other than the fact that I can't get this to work properly, I can also see that the liquid tag {{ book.title }} has no way of knowing which book title to output, even if the page.url matches.
I'm not sure if I'm on the right track here, so if anyone has any other suggestions on how this might be achievable, I'd love to hear it. Thanks in advance.
You can use Jekyll's where filter coupled with a smart include:
{% assign book = site.data.book-data | where: 'title', include.title | first %}
<div class="book-meta">
<div class="book-thumbnail">
<img
class="post-thumbnail-lg"
src="{{ book.thumbnail | prepend: 'assets/images/' | relative_url }}"
alt="{{ book.title }}"
/>
</div>
<div class="book-details">
<ul class="book-meta-list">
<li class="book-meta-item"><p>{{ book.author }}</p></li>
<li class="book-meta-item"><p><em>{{ book.title }}</em></p></li>
<li class="book-meta-item"><p>{{ book.publisher }}</p></li>
<li class="book-meta-item"><p>{{ book.pub-date }}</p></li>
</ul>
</div>
</div>
Note that I also included thumbnail info in the data file..
Then just pass the title parameter to the include (in the layout for your book's page):
{% include books.html title = 'Everything Under' %}
If you'd like to render a "listing", then just loop through and render:
{% for book in site.data.book-data %}
{% include books.html title = book.title %}
{% endfor %}
I'm learning django and I need help with my app.
In a page I have a poll: I want that after a user has voted, the poll form disappears and a div #ajaxresults appears with the updated votes for each option.
I'm using an ajax call but I can't return the updated votes.
If I call directly '/polls/4/results' I can see the right list but I can't include that block on the same page of the form.
What am I missing?
urls.py
app_name = 'polls'
urlpatterns = [
path('', views.index, name='list'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
view.py
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
#return render(request, 'polls/results.html', {'question': question})
return redirect(question.get_absolute_url())
#require_POST
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
selected_choice = question.choice_set.get(pk=request.POST['selectionId'])
selected_choice.votes += 1
selected_choice.save()
return redirect('polls:results', question_id = question_id)
detail template (extends base.html)
<form id="quiz-module" action="#">
<input type="hidden" id="pollId" name="pollId" value="{{question.id}}">
{% csrf_token %}
<fieldset>
<h2>{{ question.question_text }}</h2>
<ul>
{% for choice in question.choice_set.all %}
<li><input type="radio" name="opt" value="{{ choice.id }}" {% if forloop.first %}required {%endif%}/>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
</fieldset>
</form>
<section id="quiz-results">
<h3>Your vote</h3>
<p id="ajaxresults"></p>
<h3>All votes</h3>
<dl>
{%block updated_results %}{% endblock %}
</dl>
</section>
template vote is empty
template results (extends nothing)
{%block updated_results %}
{% for choice in question.choice_set.all %}
<dt>{{ choice.choice_text }}: </dt><dd id="choiceId-{{ choice.id }}">{{ choice.votes }}</dd>
{% endfor %}
{% endblock %}
js
var args = {
type:"POST",
url:"/polls/"+pollId+"/vote/",
data:data,
success: function( data ) {
//print your vote
results.children('#ajaxresults').html(selectionText);
form.hide('fast');
results.show('fast');
},
error: function(xhr, status, error) {
alert(error+'<br/>'+"Sorry. Can't submit your vote. Please, reload the page and try again")
},
};
$.ajax(args);
I have been trying to come up with a scheme where the comments sections refreshes in a template when a user posts comments with the comment that was just posted being included. The page must not be refreshed.
Sorry for indentation.
template -
{% extends "home/header.html" %}
{% block content %}
{% if request.user.is_authenticated %}
<form>
{% csrf_token %}
<p>Comment: </p><input type="text" name="fname" id="posted_comment">
<input type="hidden" class='meme_char_id' meme_char_id={{meme.meme_char_id}}>
<input type="submit" id ="btnSubmit" name="submit" value="Post">
</form>
<!-- displaying comments here -->
<div class="box" id="comments_div">
<h3 class="h4">{{comment_count}} comments </h4>
<ul class="list-group">
{% for comment in all_comments %}
<h5>{{ comment.commentby.username }} : </h5>
<li class="list-group-item list-group-item-success">{{ comment.comment_text }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endblock %}
Now, my JS file is -
$(document).ready(function() {
$('#btnSubmit').click(function(e) {
e.preventDefault();
var data = new FormData();
var comment = $('#posted_comment').val()
var meme_char_id = $(".meme_char_id").attr('meme_char_id')
data.append('comment', comment);
data.append('meme_char_id', meme_char_id);
$.ajax({
type: 'POST',
url: '<mydomain>/comment/',
data: data,
processData: false,
contentType: false,
success: function(reply) {
$(".comments_div").html(reply)
}
})
});
});
And finally my view is -
def comment(request):
if request.is_ajax():
comment = request.POST['comment']
meme_char_id = request.POST['meme_char_id']
this_meme = Memes.objects.get(meme_char_id=meme_char_id)
print "comment - ", comment, " meme_char_id - ", meme_char_id
new_comment = Comments(comment_text=comment, meme_id=this_meme.meme_id, commentby_id=request.user.id)
new_comment.save()
all_comments = Comments.objects.filter(meme_id=this_meme.meme_id).order_by('-created_at').values()
return HttpResponse(all_comments, content_type='application/json')
else:
raise Http404
Couple questions -
I want to return an Comments query set through ajax(don't see a point in jsonifying it, if I can just send the query set to the template.)
The queryset I am returning to template isn't picked up by the comments_div in the view.
I am a rookie so please explain step by step what I am doing wrong. Thanks.
You are sending back a JSON in a response and doing$(".comments_div").html(reply). You must be sending back a template rendered with all_comments as response on successful comment. Also, a queryset is not JSON serializable as far as I understand.
Hi I have a 'member' model that references a 'role' model. What I would like is when a user creates a 'member' they can either choose from a list of default 'roles' (that have been loaded to the database when the server starts using fixtures) or they can click on add role and then type in and click submit and the newly created role is created and assigned to the newly created 'member' object.
I would like it so that I am able to just create a role without submitting, and the role then appears in the dropdown menu for the role field in the member model. Is this where I would need to learn about AJAX to implement this feature?
Models
class MemberRole(models.Model,get_fields):
name = models.CharField(max_length = 20)
def __unicode__(self):
return self.name
class Member(models.Model,get_fields):
first_name = models.CharField(max_length = 20)
role = models.ForeignKey(MemberRole, null = True, blank = True)
View
def add_member(request):
model_url = 'member-add'
if request.method == "POST":
rform = MemberRoleForm(request.POST, instance=MemberRole())
mform = MemberForm(request.POST, instance=Member())
if rform.is_valid() and mform.is_valid():
new_role = rform.save()
new_member = mform.save(commit=False)
new_member.role = new_role
new_member.save()
return HttpResponseRedirect('members')
else:
rform = MemberRoleForm(instance=MemberRole())
mform = MemberForm(instance=Member())
return render_to_response('create_model.html', {'role_form': rform, 'member_form': mform, 'model_url': model_url,},context_instance=RequestContext(request))
snippet create_model.html
<div id = "subtemplate">
<form action="{% url model_url %}" method="POST">
{% csrf_token %}
{% if model_url == 'member-add' %}
{% for field in member_form %}
{% if field.label == 'Role' %}
<div id="roleExistsMemberForm">
<button type="button" onclick="showDiv()">Add Role</button>
{{ field.errors }}
{{ field.label_tag }} {{ field }} {{ field.help_text }}
</div>
{% else %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endif %}
{% endfor %}
<div Id="addRoleOnMemberForm">
{% for field in role_form %}
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% endfor %}
</div>
{% else %}
<ul>
<li>{{ form.as_ul }}</li>
</ul>
{% endif %}
<div id="centerbuttons">
<input id="save_contact" type="submit" value="Add"/>
</div>
</form>
Yes, Ajax would be the first point of call for something like this. You can submit the form in the background to a Django view, the view could then respond with a fresh list of options to re-populate the drop down list with.