I would like to construct (constructing a table is easy, I do it inside templates) a table and choose a row from it. I am using pyramid as a framework and I think somehow I need to make the templates talk to the model, but I am not sure how. Can someone show me an example or direct me to link where I can see an explanation and an example (I couldn't find one). This is my HTML for the table:
<table border="1">
<tr>
<th>Course Number</th>
<th>Course Name</th>
</tr>
<tr>
<td>111</td>
<td>What ever the name is</td>
</tr>
</table>
Making some assumptions, what you describe would be handled via ajax, which is a technology based on javascript, and outside the scope of they Pyramid framework but easily added. Below is a very simple example of what the HTML might look like.
<html>
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('.row').bind('click',function(){
var row = $(this);
options = {
url:"/some_pyramid_url_to_a_view/",
type:"get",
data:{'row_id':$(this).attr('id')},
success:function(event){$(row).text(event.data.method_result)},
error:function(){alert('error')},
dataType:'json'
}
$.ajax(options);
});
});
</script>
</head>
<body>
<table>
<tr id="${row.id}" class="row">
<td>Row</td>
</tr>
</body>
</html>
The code between the tags is the javascript, here I am using the jQuery library to create the ajax call to the url '/some_pyramid_url_to_a_view/' which is tied to the view function 'some_pyramid_view'. I'm binding a click event to the row element in the table using the jQuery selector on the class 'row' "$('.row').bind('click',..." the event is then handled by the function block "function(){...}" immediately following. I'm setting up the call in the options object and passing the row id in the data "data:{'row_id':$(this).attr('id')}," where the code "$(this).attr('id')" is accessing the id set in the template '...' Finally I send of the request to the view using '$.ajax(options)'.
import json
#view_config(xhr=True)
def some_pyramid_view(request):
json_dict = dict()
session = DBSession()
if request.method == 'GET':
row_id = request.GET['row_id']
row = session.query(Row).get(row_id)
json_dict['method_result'] = row.some_method_call()
return Response(json.dumps(json_dict))
There is another piece here, JSON is being used to communicate back to the javascript from the Pyramid view. JSON stands for JavaScript Object Notation and is a data-interchange format. I create a Python dictionary and using the 'json' package convert it to JSON which I send back to the 'success' call back function in the javascript with the results of the method call. In the success call back function I update the text in the table row with the result of the method call in the view. '$(row).text(event.data.method_result)'
This might be more than you were expecting but it is well worth learning how to use ajax to add functionality to your websites.
Related
I have a Python variable whose value is a string of text and would like to edit that value via Javascript.
I have no idea how to go about doing this.
Attempts:
function changeValue(val) {
val = 'new text';
}
<textarea placeholder="some text">{{ changeValue({{ result }}) }}</textarea>
<textarea placeholder="some text">
{{ result }}
</textarea>
What I want: I have some text (result) being added and would like to check if the text is empty. If so, I want to show the placeholder text.
The issue: Although I can check if the value is empty, when I try to print that result out it reads none
Thanks to all!
You do not need to call the JavaScript function from the HTML file. There are several approaches you can take:
1. Store the variable in HTML metadata:
<meta id="result_var" data-result={{result}}>
And then get the data in JavaScript:
result = document.getElementById("result_var").value;
2. Keep the variable in the tag where it's supposed to be and get it from there in JavaScript:
<textarea placeholder="some text" id="result-var"> {{result}} </textarea>
And then get it in JavaScript:
let result = document.getElementById("result-var");
3. Query it from your API: You can create a route in your Flask app that returns JSON data with the variable you need and then get that data to your JavaScript file by sending a request to your API.
4. Jinja format: I've seen solutions that involve just using the variable as if it was a jinja variable in JavaScript like this: let result = JSON.parse('{{ result | tojson }}');. But I haven't been able to get this working properly, not sure why.
I hope this helps!
Problem
I am trying to display image-files using AJAX in Flask. More specifically, I want to display an image once a button is clicked and display the next image once the button is clicked again (like a slide-show). The filenames of the images are stored in my database. I query the database to get a list of filenames for the current user and combine each filename with the rest of the path (path to where the images are stored on disk) in order to display the image.
So far, I manage to get the first image of the current user. However, I can't figure out a way to keep track of which image is the next one to show.
I tried using a global variable as a counter (file_counter) which should serve as an index. I want to increase file_counter by 1 each time an ajax request is made in order to get the next file but the counter does not increase upon subsequent calls nor does it throw an error.
Question
How do I need to initialize the global variable (file_counter) in order for it to store its value across multiple calls? Furthermore, is the usage of global variables the correct way of doing this?
HTML
<div id="ajax-field"></div>
<button class="btn btn-block" id="next-button"><p>Next Image!</p></button>
AJAX:
$('#next-button').click(function(){
$("#ajax-field").text("");
$.ajax({
url: "/get_data",
type: "POST",
success: function(resp){
$('#ajax-field').append(resp.data);
}
});
});
Routing:
global filenames
global file_count
#app.route("/get_data", methods=['POST'])
def get_data():
try: # Is intended to fail on the first run in order for the global variables to be initialized. However it keeps failing on subsequent runs
display_img = filenames[file_count]
file_count +=1
except:
filenames = []
# current_user.uploads returns all file-objects of the current user
user_uploads = current_user.uploads
for file in user_uploads:
# file.filename returns the respective filename of the image
filenames.append(file.filename)
#filenames is now a list of filenames i.e. ['a.jpg','b.jpg','c.jpg'...]
display_img = filenames[0]
file_count = 1
path = "image_uploads/4_files/"+display_img
return jsonify({'data': render_template('ajax_template.html', mylist = path)})
ajax_template.html:
<ul>
{% block content %}
<li>
<img id="selected-image-ajax" src="{{url_for('static',filename=mylist)}}" class="img-thumbnail" style="display:block; margin:auto;"></img>
</li>
{% endblock content %}
</ul>
As #roganjosh pointed out, a session is the optimal way to store information across multiple requests. This solution presents an implementation of the photo display using flask.session to store the counter:
import flask, random, string
app = flask.Flask(__name__)
app.secret_key = ''.join(random.choice(string.printable) for _ in range(20))
#to use flask.session, a secret key must be passed to the app instance
#app.route('/display_page', methods=['GET'])
def display_page():
'''function to return the HTML page to display the images'''
flask.session['count'] = 0
_files = [i.filename for i in current_user.uploads]
return flask.render_template('photo_display.html', photo = _files[0])
#app.route('/get_photo', methods=['GET'])
def get_photo():
_direction = flask.request.args.get('direction')
flask.session['count'] = flask.session['count'] + (1 if _direction == 'f' else - 1)
_files = [i.filename for i in current_user.uploads]
return flask.jsonify({'photo':_files[flask.session['count']], 'forward':str(flask.session['count']+1 < len(_files)), 'back':str(bool(flask.session['count']))})
The display_page function will be called when a user accesses the /display_page route and will set the count to 0. get_photo is bound to the /get_photo route and will be called when the ajax request is sent.
photo_display.html:
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<div class='image_display'>
<img src="{{photo}}" id='photo_display' height="100" width="100">
<table>
<tr>
<td class='back'></td>
<td class='forward'><button id='go_forward' class='navigate'>Forward</button></td>
</tr>
</table>
</div>
</body>
<script>
$(document).ready(function(){
$('.image_display').on('click', '.navigate', function(){
var direction = 'b';
if ($(this).prop('id') === 'go_forward'){
direction = 'f';
}
$.ajax({
url: "/get_photo",
type: "get",
data: {direction: direction},
success: function(response) {
$('#photo_display').attr('src', response.photo);
if (response.back === "True"){
$('.back').html("<button id='go_back' class='navigate'>Back</button>")
}
else{
$('#go_back').remove();
}
if (response.forward === "True"){
$('.forward').html("<button id='go_forward' class='navigate'>Forward</button>")
}
else{
$('#go_forward').remove();
}
},
});
});
});
</script>
</html>
The javascript in display_page.html communicates with the backend, and updates the img tag src accordingly. The script adds or removes the navigation buttons, depending on the current count value.
Demo:
To test the solution above, I created an image folder to store random photographs to display:
I'm using AppEngine to create a page that I would like to update from the program. Specifically, I am getting some market data and would like to have a table (or something else appropriate) that shows current prices. Let me be clear: I am new to this and think my problem is that I'm not asking the question well enough to find a good (best) answer. I'm not even sure AppEngine is necessarily the way to go. I'll also caveat that I've been learning via Udacity so if code looks familiar -- kudos to Steve Huffman.
I've created the page via jinja2 and I've managed to wrangle the appropriate libraries and sandbox parameters to get market updates. I've created an html table and passed in a dictionary with values for exchanges and bid/ask pairs. The table creates fine -- but when I render again, I get tables repeating down the page rather than one table with updating market prices.
Here is the html/jinja2 (I ditched all the styling to make it shorter):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Table template</title>
</head>
<body>
<h1>Table template</h1>
<table>
{% for exch in mkt_data %}
<tr>
<td> <div>{{exch}}</div></td>
<td> <div>{{mkt_data[exch][0]}}</div></td>
<td><div>{{mkt_data[exch][1]}}</div></td>
</tr>
{% endfor %}
</table>
</body>
</html>
Here is the code:
import os
import jinja2
import webapp2
import ccxt
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(template_dir),
autoescape=True)
class Handler(webapp2.RequestHandler):
def write(self, *a, **kw):
self.response.out.write(*a, **kw)
def render_str(self, template, **params):
t = jinja_env.get_template(template)
return t.render(params)
def render(self, template, **kw):
self.write(self.render_str(template, **kw))
class MainPage(Handler):
def get(self):
self.render("table.html", mkt_data=btc)
for x in range(3):
for exch in exchanges:
orderbook=exch.fetch_order_book('BTC/USD')
bid = orderbook['bids'][0][0] if len(orderbook['bids'])>0 else None
ask = orderbook['asks'][0][0] if len(orderbook['asks'])>0 else None
btc[exch.id]=[bid,ask]
self.render("table.html", mkt_data=btc)
gdax = ccxt.gdax()
gemini = ccxt.gemini()
exchanges = [gdax, gemini]
btc = {"gemini":[0,1], "gdax":[1,2]}
for exch in exchanges:
exch.load_markets()
app = webapp2.WSGIApplication([('/', MainPage)], debug=True)
I have 2 questions:
First, why am I getting the table repeating? I think I know why, but I want to hear a formal reason.
Second, what should I be doing? I originally started learning javascript/node but then it seemed very hard to wrap all the appropriate libraries (was looking into browserify but then thought appengine may be better so I could more easily host something for others to see). I tried integrating some javascript but that did not get me anywhere. Now I've run into Firebase but before I go learn yet another "thing" I wanted to ask how other people do this. I'm certain there are multiple ways but I'm new to web programming; I'm viewing a web page as a nice UI & delivery mechanism.
Some add'l notes: using Ubuntu, virtualenv, ccxt library (for cryptocurrency).
edit: I checked Dan's answer because it offered a solution. I'd love to hear about whether Firebase is "a" more correct solution rather than auto-refreshing.
The repeated table is the result of the multiple self.render() calls inside your MainPage.get() - both above and repetead ones inside the for loop(s).
Update your code to make a single such call, after the for loops building the template values (at the end of MainPage.get())
Created a JSBin that demostrated the problem: http://jsbin.com/kukehoj/1/edit?html,js,console,output
I'm creating my first REST-powered website. The backend is in Python (Django REST Framework), and seems to be working fine. I'm trying to make the front-end get the comments for the posts, but its not working.
HTML Imports
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js"></script>
scripts.js
function Comment(data) {
this.body = ko.observable(data.responseText)
}
function Post(data) {
this.title = ko.observable(data.title)
this.body = ko.observable(data.body)
var self = this;
self.comments = ko.observableArray([])
self.comments(($.map(data.comments, function(link) { // Map the data from
return $.getJSON(link, function(data) { return new Comment(data)}) //These requests
})))
}
function PostViewModel() {
// Data
var self = this;
self.posts = ko.observableArray([])
// Get the posts and map them to a mappedData array.
$.getJSON("/router/post/?format=json", function(allData) {
var mappedData = $.map(allData, function(data) { return new Post(data)})
self.posts(mappedData)
})
}
ko.applyBindings(new PostViewModel());
Server data:
[{ "title":"-->Title here<--",
"body":"-->Body here<--",
"comments":[
"http://127.0.0.1:8000/router/comment/6/?format=json",
"http://127.0.0.1:8000/router/comment/7/?format=json",
"http://127.0.0.1:8000/router/comment/8/?format=json",
"http://127.0.0.1:8000/router/comment/9/?format=json"]
}]
where each of the links leeds to:
{"body":"-->Body here<--"}
index.html
<div class="col-lg-7" data-bind="foreach: { data: posts, as: 'posts' }">
<h3 data-bind="text: title"></h3>
<p data-bind="text: body"> </p>
<span data-bind="foreach: { data: comments(), as: 'comments' }">
<p data-bind="text: comments.body"></p>
</span>
</div>
(There is a lot more HTML, but i removed the irrelevant parts)
Everything is working fine, except from that the comments seem to be in the wrong format.
The chrome console shows JSON "responseText" bound to each of the comment object values.
Wrong format
I'm sorry if this is a stupid question, but I have tried everything - but it doesn't work. (I'm a noob)
There is nothing wrong with your sample code you provided except the part you have this.body = ko.observable(data.responseText) while your data does not contain a responseText in your sample commentData object . if you replace commentData object with var commentData = {"responseText":"-->Body here<--"} it works.
Note: this part
<span data-bind="foreach: { data: comments(), as: 'comments' }">
<p data-bind="text: comments.body"></p> // comments.body => body
</span>
on your question is wrong but you have it correct on your sample code .It should be
<span data-bind="foreach: { data: comments(), as: 'comments' }">
<p data-bind="text: body"></p>
</span>
Here is a working version of your sample :https://jsfiddle.net/rnhkv840/26/
I assume you are using Django Rest Framework, so the JSON structure you get for your posts is done automatically by your serializer based on your model fields.
Back to the frontend, I have not used knockout js before, but what you require is to load the comments using another controller. Either you do it one by one using the links provided by your main resource (this can result in lots of queries sometimes), or you create a filter on your comments endpoint which will allow you to retrieve comments for a specific post.
Ever considered using the django REST framework? It can help you serialize all you models with a simple viewset. Check out the docs.
So found the actual problem. The way the JavaScript read the data from the server, ment that since there was only one value for the comments, the data property of a comment was the variable storing the body of the comment. Not the data.body.
Suppose I want to render a page (not just JSON) using Flask with some specific data that I fetch from the database. For example
display_data.html includes:
<script src='display_data.js'></script>
...
<h1>Data display page!</h1>
<div id="chartContainer"></div>
display_data.js:
$(function() {
draw_chart($("#chartContainer"), json_data);
//draw_chart is defined elsewhere and json_data is what I want to pass in
});
Python:
#app.route('/<data_id>')
def get_display_data_page(data_id):
data = get_data_by_id(data_id)
return render_template('display_data.html', data = data)
I think that if I want to just "render template", I'd have to include elsewhere in display_data.html the following:
<script>window.json_data = {{ data | tojson | safe}}</script>
This pattern smells bad: I'm leaving an object on the global namespace (so that my JS file can access it), displaying the data as plain text, and rendering a string in that is parsed into JSON so the JS can use it. Looks bad but this does work.
Two other options:
Return the data with AJAX. Given the title of this post I'm specifically trying to avoid ajax. The reason for this is mainly that I'm building a mobile site and want to reduce the number of pings back to the server. I'm also thinking (perhaps more metaphysically) about encapsulating the page: once you have it, you have all of it.
Render my JS file via Flask and Jinja. This seems like a bummer because I'd have to then write a route down and render the JS based on the same logic that I have in the get_display_data_page: looking up the data by its id, etc. Code duplication and dynamic JS sound like big no-no's to me.
Is there a known pattern to doing this well?
There's no need to leave data in the global scope if you don't want to. In your template you can do something like this:
<script>
function registerTask(f, args) {
$(function() {
f.call(this, args);
});
}
{% for name, args in js_tasks %}
registerTask({{name}}, {{ args | tojson | safe }});
{% endfor %}
</script>
Then, in your JS file, redefine draw_chart to just take the data (or have a wrapper around it that you use as your task registry name):
function draw_chart_task(data) {
draw_chart($('#chartContainer'), data);
}
Finally, in your controller, simply provide the data and the task name as a tuple:
return render_template('display_data.html', js_tasks=[('draw_chart_task', data)])
This ensures that your JavaScript is not just plucking its dependencies out of the global scope, and you are not making extra network calls.
The data is visible in the raw text output of the page, but it is visible if you make an AJAX call too, you just have to look in a different panel of your browser's developer's tools to see it.