The data has to be refreshed without page reload. Originally data is appeared on html with jinja2.
#app.route('/personal_account', methods=['POST'])
def welcome():
login = request.form['login']
data = get_default_user_data(login)
# ... processing
return render_sidebar_template("personal_account.html", data=data)
According to these data graph is building with chartist.js.
personal_account.html
<div id="data">
<ul id="consumed_values">
{% set count = 0 %}
{% for i in data.consumed_values %}
<li>{{ data.consumed_values[count] }}</li>
{% set count = count + 1 %}
{% endfor %}
</ul>
</div>
<canvas width="800" height="600" id="canvas"></canvas>
<button id="button">Update</button>
I need to update data. I am using ajax.
The function "request" make a post request to the server to the function get_selected_values in Python.
This function gives new data. But new data doesn't display in jinja2 on page. The data is still old.
personal_account.js
window.onload = draw();
function draw() {
var consumed_values = document.querySelectorAll('ul#consumed_values li');
var values = new Array();
for (var i = 0; i < consumed_values.length; i++) {
console.log(consumed_values[i].innerHTML);
values[i] = consumed_values[i].innerHTML;
}
var numbers = new Array();
for(var i=0; i<consumed_values.length; i++)
{
numbers[i]=i+1;
console.log(numbers[i]);
}
var ctx = document.getElementById('canvas').getContext('2d');
var grapf = {
labels : numbers,
datasets : [
{
strokeColor : "#6181B4",
data : values
}
]
}
new Chart(ctx).Line(grapf);
}
document.getElementById('button').onclick=function () {
request();
}
function reques() {
var first = selected[0];
var second = selected[1];
first.month = first.month+1;
second.month = second.month+1;
$.ajax({
type: 'POST',
url: '/get_selected_values',
success: function(response) {
alert('Ok');
draw();
},
error: function() {
alert('Error');
}
});
}
Function get_selected_values()
#app.route('/get_selected_values', methods=['POST'])
def get_selected_values():
# ...
data = fetch_selected_date(start_date=start_date, end_date=end_date, login=current_user.get_id())
if data:
# return jsonify({'result': True, 'data': data}) # does not work this way
# return jsonify({'result': False, 'data': []})
return render_sidebar_template("personal_account.html", data=data, result=1)
How to succeed in data's update and graph's rebuild?
EDIT 1
I am using the first version of get_selected_values function.
The request function look like this:
...
success: function(response) {
alert('Успешно получен ответ:!'+ response.data);
document.getElementById('consumed_values').innerHTML = response.data;
draw();
},
...
Data is updating successfully, but graph looks the same. How to fix?
OK here's my outlook on this. You're on the right track and there is a way to update the element without the need to re-draw the page in this instance. What's happening is that you are returning data from your get_selected_values() method but not doing anything with it once it's returned to your AJAX request.
So firstly, I'm going to draw your attention to your AJAX request:
$.ajax({
type: 'POST',
url: '/get_selected_values',
success: function(response) {
alert('Ok');
draw();
},
error: function() {
alert('Error');
}
});
When you're getting a successful response from this, you're seeing your "OK" alert in the UI, right? However nothing updates in the UI despite you calling on the draw() method?
You won't want to return a render_template from your Flask function in this case. You were already on the right track with returning JSON from your function:
if data:
# return jsonify({'result': True, 'data': data}) # does not work this way
When you return your JSON data, it will be stored in the response variable in your success function. If you're unsure of exactly what's going into that response variable then output its contents with something like alert(JSON.stringify(response)) in the success function of your AJAX request. From here you will see your data returned to your method.
Now you need to decide how you want to use that data to update your <div id="data"> element in your UI. You can do this just using JavaScript with a series of document.getElementById('element_id').innerHTML statements or such-like so that your element is populated with all of the updated data from your response.
This will auto-update the data you wish to have displayed without the need to refresh the page.
Now that you've done that, invoke your draw() function again and it should now use the updated data.
I hope this helps set you down the right path with this one!
AFTER EDIT 1
When you're originally populating <div id="data"> you are using a loop to populate a series of <li> tags in the element with your data.
When you are updating this element with your new data, you are just using .innerHTML to re-populate the parent <ul> element.
Your draw() method is looking to the data stored in the <li> elements.
Are you absolutely certain that, after you perform your update, your <div id="data"> element is in exactly the same (ie. expected) format to work with your draw() method? In that it's still in the structure:
<div id="data">
<ul id="consumed_values">
<li>Your updated data here...</li>
<li>More updated data...</li>
</ul>
</div>
This is the element structure that your draw() method is expecting to find. It's pulling its data in from each individual <li> element in the list. So these are the elements which need to store your updated values.
Related
I have a small app that's written in Javascript that's a to-do list. The frontend is simple Javascript that takes data from an array, then allows the user to add items to the array (post data). When they click on an item, it should be deleted it
Now, it sort of works. So when the user enters a item and press enter or clicks submit, it lists the item twice?!
When the user clicks an item to remove it, it only removes when the page is refreshed (F5). The location.reload() method doesn't appear to fire, or the success isn't registering
Here is the front end, showing the duplication:
Here is the js that controls the post and deletes (todo_list.js):
//These are ajax requests
//in the controller post method we will be using this code
$(document).ready(function(){
//when click submit
$('form').on('submit', function(){
//create item
let item = $('form input');
//create todo with value from item above
//add to data array in the controller
let todo = {item: item.val()};
//send post request to post in controller
$.ajax({
type: 'POST',
url: '/todo',
//pass todo as data
data: todo,
success: function(data){
//do something with the data via front-end framework
//reload page when new item is added
location.reload();
}
});
return false;
});
$('li').on('click', function(){
//replace spaces with hyphens
var item = $(this).text().replace(/ /g, '-');
$.ajax({
type: 'DELETE',
url: '/todo/' + item,
success: function(data){
location.reload();
}
});
});
});
Here's the ejs file for the webpage (todo.ejs):
<html>
<head>
<title>To-do list</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
<!-- below works because of the middleware setting for static files -->
<script src="/assets/todo_list.js"></script>
<link href="/assets/styles_example_app.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>My todo list</h1>
<div id="todo-table">
<form method="POST">
<input type = "text" name="item" placeholder="Add new item..." required />
<button type = "submit">Add items</button>
</form>
<ul>
<% for(let i = 0; i < todos.length; i++){ %>
<li><%= todos[i].item %></li>
<% } %>
</ul>
</div>
</body>
<script src="/assets/todo_list.js"></script>
</html>
In the above, the for loop is needed to show the existing items in the array (data), here is the controller:
const bodyParser = require('body-parser');
//storing some data to display on the webpage. This is the array we're working with
let data = [{item: 'get milk'}, {item: 'walk dog'}, {item: 'kick Joey'}];
let urlencodedParser = bodyParser.urlencoded({extended: false});
module.exports = function(app){
//now we're exporting and calling in app.js, we can make the routes
app.get('/todo', function(req, res){
//render a get request when user visit /todo in browser
//pass the data from the above data object onto the page
res.render('todo', {todos: data});
});
//handler for post request for when user enters to do list item todo_list.js
app.post('/todo', urlencodedParser, function(req, res){
//grab data from array and push user input data to array
//added new item to data array
data.push(req.body);
//once added, render page
res.redirect('/todo');
});
//handler for deleting the files
app.delete('/todo/:item', function(req,res){
//filter out item that we're trying to delete
data = data.filter(function(todo){
// bottom is returning true or false
// if false, remove from array
return todo.item.replace(/ /g, '-') !== req.params.item;
res.redirect('/todo');
})
});
}
What i'm confused about is how the input post data can be duplicated, and why the deleted items, when clicked, don't reload the page. If I manually reload, they're deleted. I feel like i'm missing something small since it all sort of works, albeit badly
Does anyone have any suggestions?
So far i've tried changing the jquery call to google (one suggestion I found here), also in the EJS file specifying post in the form tag as the method
Thanks
Quick solution (a hack not a real solution):
$.ajax({
type: 'DELETE',
url: '/todo/' + item,
always: function(data){
location.reload();
}
});
Above could work because it will call the callback whatever the response is, but is is not a proper solution, the proper solution would be to make sure you have a proper response code returned according to the HTTP STATUS CODES: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes and it is properly handled via success/error callbacks.
Third, you are using ajax to avoid reloads, example: you could return the up-to-date list of items on every api call and re-render the component after every api call, not reload.
I am currently coding within a ViewComponent (ViewComponent1) view. Within this View, I have listed a few items:
As you can see, the channels 11, 12, 13 and 14 are clickable. Each channel has some additional information (OBIS, avtalsid.. etc). What I´m trying to do is to invoke ViewComponent2, within ViewComponent1, and pass along some of the data, based on the clicked item.
What I tried to do is to create another View called "Test" and within that View invoke ViewComponent2 along with its parameters, like this:
<div class="row">
<div class="col-md-2 canalstyle">
<a asp-controller="Customer" asp-action="Test" asp-route-pod="#item.STATION"
asp-route-idnr="#item.IDNR" asp-route-kanal="#item.KANAL" asp-route-start="#Model.start"
asp-route-end="#Model.end"> #Html.DisplayFor(modelItem => item.KANAL)</a>
</div>
</div>
This works, but this method redirects me away from my current View (ViewComponent 1). I don't want that. I want the current view to load the additional information from ViewComponent2.
My function that runs the ajax:
function myFunction() {
var data = JSON.stringify({
'idnr': id,
'start': this.start,
'end': this.end
});
$.ajax({
url: '#Url.Action("Test2","Customer")',
type: 'GET',
data: { idnr: id, start: this.start, end: this.end },
contentType: 'application/json',
success: handleData(data)
})
};
function handleData(data) {
alert(data);
var url = $(this).attr("href");
var $target = $(this).closest("div").find(".details");
$.get(url, function (res) {
$target.html(res);
});
//do some stuff
}
And my Test2 Action:
public async Task<IActionResult> Test2(string idnr, string start, string end)
{
ServiceClient r2s = new R2S.ServiceClient();
R2S.Konstant[] kData = r2s.GetKonstantListAsync(new string[] { "IDNR" }, new string[] { idnr}).Result; // mätarnummer in... --> alla konstanter kopplade till denna.
return ViewComponent("MeterReader2", new { k = kData[0], start = start, end = end });
}
I am trying to target the same DOM.. Any ideas?
Your current code is rendering links (a tags) and normally clicking on a link will do a new GET request, which is what you are seeing , the redirect to the new action method.
If you do not want the redirect, but want to show the result of the second view component in same view, you should use ajax.
For example, If you want to show the result of second view component just below each link, you may add another html element for that. Here i am adding an empty div.
<div class="row">
<div class="col-md-2 canalstyle">
<a class="myClass" asp-controller="Customer" asp-action="DetailsVc"
asp-route-id="#item.Id" > #item.KANAL</a>
<div class="details"></div>
</div>
</div>
Here i just removed all those route params you had in your orignal question and replaced only with on param (id) . Assuming your items will have an Id property which is the unique id for the record(primary key) and using which you can get the entity (from a database or so) in your view component to get the details.
This will generate the link with css class myClass. You can see that, i used asp-action attribute value as "DetailsVc". We cannot directly use the view component name in the link tag helper as attribute value to generate the href value. So we should create a wrapper action method which returns your view component result such as below
public IActionResult DetailsVc(int id)
{
return ViewComponent("DetailsComponent", new { id =id });
}
Assuming your second view components name is DetailsComponent and it accepts an id param. Update the parameter list of this action method and view component as needed. (but i suggest passing just the unique Id value and get details in the server code again)
Now all you have to do is have some javascript code which listen to the click event on those a tags and prevent the normal behavior (redirect) and make an ajax call instead, use the ajax call result to update the details div next to the clicked link.
You can put this code in your main view (or in an external js file without the #section part)
#section Scripts
{
<script>
$(function() {
$("a.myClass").click(function(e) {
e.preventDefault();
var url = $(this).attr("href");
var $target = $(this).closest("div").find(".details");
$.get(url,function(res) {
$target.html(res);
});
});
});
</script>
}
I have a select box in an item edit page, which i would like to be populated via an Ajax call with the saved values.
<script type="text/javascript">
$(document).ready(function() {
$('#editPrefabLineclassBox').on('change', function() {
var selected = this.value;
$.ajax({
url: '/edit-prefab/,
type: 'POST',
data: {
csrfmiddlewaretoken: '{{ csrf_token }}',
lineclassSelected: selected
},
success: function(data) {
var name, select, option;
select = document.getElementById('editPrefabNameBox');
select.options.length = 0;
for (name in data) {
if (data.hasOwnProperty(name)) {
select.options.add(new Option(data[name], name));
}
}
}
});
});
})
</script>
The url i am using in the call is /edit-prefab/. The problem i am having is, the url of the page in Django is actually /edit-prefab/{{ material_item.id }}, only i am not sure how to pass this id to javascript to use in the Ajax call. With just the /edit-prefab/, the page is not found.
After populating the select with the list of items, i would like to have preselected the saved values of the item being edited. I am sure i could populate everything as needed. Its just the setting up of the url that has me a little confused
I have tried passing the id of the item through the view to the template with JSON.dumps, and then parse the variable in JS to use in the url, but i keep getting an unexpected column error when parsing, as from what i know only a dict can be parsed correctly with JSON.
Is there anyone who could please help with this?
EDIT:
def editprefabitem(request, materialitem_id):
context = dict()
mat_item = MaterialItem.objects.get(id=materialitem_id)
context['itemid'] = json.dumps(mat_item.id)
context['lineclass'] = json.dumps(mat_item.lineclass)
context['itemname'] = json.dumps(mat_item.name)
context['diameter'] = json.dumps(mat_item.diameter)
context['quantity'] = json.dumps(mat_item.quantity)
if request.method == 'POST':
if 'lineclassSelected' in request.POST:
lclass = Lineclass.objects.filter(lineclassname=request.POST['lineclassSelected'])\
.values_list('itemname', flat=True).distinct()
request.session['lineclassselected'] = request.POST['lineclassSelected']
lineclass = valuesquerysettodict(lclass)
return HttpResponse(json.dumps(lineclass), content_type='application/json')
if 'itemSelected' in request.POST:
item = Lineclass.objects.filter(itemname=request.POST['itemSelected'])[0]
diams = Lineclass.objects.filter(itemname=item.itemname).values_list('dn1', flat=True).distinct()
request.session['itemselected'] = request.POST['itemSelected']
diameters = valuesquerysettodict(diams)
return HttpResponse(json.dumps(diameters), content_type='application/json')
if 'diamSelected' in request.POST:
request.session['diameterselected'] = request.POST['diamSelected']
if 'editPrefabQuantityBox' in request.POST:
code = Lineclass.objects.filter(lineclassname=request.session['lineclassselected'])\
.filter(itemname=request.session['itemselected']).filter(dn1=request.session['diameterselected'])[0]\
.code
mat_item.name = request.session['itemselected'],
mat_item.type = 'Prefabrication',
mat_item.lineclass = request.session['lineclassselected'],
mat_item.diameter = request.session['diameterselected'],
mat_item.quantity = request.POST['editPrefabQuantityBox'],
mat_item.workpack = Workpack.objects.get(id=request.session['workpackselected']),
mat_item.code = code,
mat_item.datecreated = datetime.datetime.today(),
mat_item.createdby = request.user.username
mat_item.save()
return HttpResponseRedirect('/prefabs/')
return render_to_response('editprefab.html', context, context_instance=RequestContext(request))
The context['itemid'], context['lineclass'] etc, is where i am grabbing the current values of the item and trying to send them through to the template to be parsed by javascript to set the default values for editing in the select boxes, and provide the items id in the url.
The valuesquerysettodict() function, is a small snippet i found, to convert a Models, values_list into a JSON serializable dict to populate the select based on the parameter that was sent through from Ajax. The reason i am using it, is if i return Lineclass.objects.all(), there are a lot of items in the queryset, with the same name, but different itemcode, so i am using a values_list to try and get unique item names to use with the select.
I am sure i am going wrong somewhere i am just not sure where.
thank you for any help you could give.
So I assume you're making your AJAX call at the click of a submit button. Either way, you can supply the Django variable in the value attribute of any tag and retrieve it like this.
In the value attribute of your submit button, pass the Django ID as such:
<button type="submit" value="{{ material_item.id }}" id="submit-button"></button>
Now, in your AJAX request, you can retrieve the ID and send it with your AJAX request like this:
$(document).ready(function(event){
$(document).on('click', '#submit-button' , function(event){
event.preventDefault();
var pk = $(this).attr('value');
data:{
'id': pk,
}
....
});
});
I have the skeleton of a chat page but am having issues tying it all together. What I'm trying to do is have messages sent to the server whenever the user clicks send, and also, for the messages shown to update every 3 seconds. Any insights, tips, or general comments would be much appreciated.
Issues right now:
When I fetch, I append the <ul class="messages"></ul> but don't want to reappend messages I've already fetched.
Make sure my chatSend is working correctly but if I run chatSend, then chatFetch, I don't retrieve the message I sent.
var input1 = document.getElementById('input1'), sendbutton = document.getElementById('sendbutton');
function IsEmpty(){
if (input1.value){
sendbutton.removeAttribute('disabled');
} else {
sendbutton.setAttribute('disabled', '');
}
}
input1.onkeyup = IsEmpty;
function chatFetch(){
$.ajax({
url: "https://api.parse.com/1/classes/chats",
dataType: "json",
method: "GET",
success: function(data){
$(".messages").clear();
for(var key in data) {
for(var i in data[key]){
console.log(data[key][i])
$(".messages").append("<li>"+data[key][i].text+"</li>");
}
}
}
})
}
function chatSend(){
$.ajax({
type: "POST",
url: "https://api.parse.com/1/classes/chats",
data: JSON.stringify({text: $('input1.draft').val()}),
success:function(message){
}
})
}
chatFetch();
$("#sendbutton").on('click',chatSend());
This seems like a pretty good project for Knockout.js, especially if you want to make sure you're not re-appending messages you've already sent. Since the library was meant in no small part for that sort of thing, I think it would make sense to leverage it to its full potential. So let's say that your API already takes care of limiting how many messages have come back, searching for the right messages, etc., and focus strictly on the UI. We can start with our Javascript view model of a chat message...
function IM(msg) {
var self = this;
self.username = ko.observable();
self.message = ko.observable();
self.timestamp = ko.observable();
}
This is taking a few liberties and assuming that you get back an IM object which has the name of the user sending the message, and the content, as well as a timestamp for the message. Probably not too far fetched to hope you have access to these data elements, right? Moving on to the large view model encapsulating your IMs...
function vm() {
var self = this;
self.messages = ko.observableArray([]);
self.message = ko.observable(new IM());
self.setup = function () {
self.chatFetch();
self.message().username([user current username] || '');
};
self.chatFetch = function () {
$.getJSON("https://api.parse.com/1/classes/chats", function(results){
for(var key in data) {
// parse your incoming data to get whatever elements you
// can matching the IM view model here then assign it as
// per these examples as closely as possible
var im = new IM();
im.username(data[key][i].username || '');
im.message(data[key][i].message || '');
im.timestamp(data[key][i].message || '');
// the ([JSON data] || '') defaults the property to an
// empty strings so it fails gracefully when no data is
// available to assign to it
self.messages.push(im);
}
});
};
}
All right, so we have out Javascript models which will update the screen via bindings (more on that in a bit) and we're getting and populating data. But how do we update and send IMs? Well, remember that self.message object? We get to use it now.
function vm() {
// ... our setup and initial get code
self.chatSend = function () {
var data = {
'user': self.message().username(),
'text': self.message().message(),
'time': new Date()
};
$.post("https://api.parse.com/1/classes/chats", data, function(result) {
// do whatever you want with the results, if anything
});
// now we update our current messages and load new ones
self.chatFetch();
};
}
All right, so how do we keep track of all of this? Through the magic of bindings. Well, it's not magic, it's pretty intense Javascript inside Knockout.js that listens for changes and the updates the elements accordingly, but you don't have to worry about that. You can just worry about your HTML which should look like this...
<div id="chat">
<ul data-bind="foreach: messages">
<li>
<span data-bind="text: username"></span> :
<span data-bind="text: message"></span> [
<span data-bind="text: timestamp"></span> ]
</li>
</ul>
</div>
<div id="chatInput">
<input data-bind="value: message" type="text" placeholder="message..." />
<button data-bind="click: $root.chatSend()">Send</button>
<div>
Now for the final step to populate your bindings and keep them updated, is to call your view model and its methods...
$(document).ready(function () {
var imVM = new vm();
// perform your initial search and setup
imVM.setup();
// apply the bindings and hook it all together
ko.applyBindings(imVM.messages, $('#chat')[0]);
ko.applyBindings(imVM.message, $('#chatInput')[0]);
// and now update the form every three seconds
setInterval(function() { imVM.chatFetch(); }, 3000);
});
So this should give you a pretty decent start on a chat system in an HTML page. I'll leave the validation, styling, and prettifying as an exercise to the programmer...
I have a Map which contains the status information as a value for a object.The Ajax should poll the map and update the status on the grid.I am facing issue in updating the same.
The code snippet I am using is:
setInterval(
$.post("./listStatus.action", {
method: "getStatus",
}, function( data, success ) {
if ( success == "success" ) {
var rowsIds = statusGrid.getDataIDs();
console.log("Row Ids:"+rowsIds);
for(var i=0;i<data.statusList.length;i++){
var rowData=statusGrid.jqGrid('getRowData',data.statusList[i].rowID);
rowData["Status"] =data.statusList[i].Status;
statusGrid.jqGrid('setRowData', data.statusList[i].rowID, rowData);
}
}
}),5000);
On first call its able to show the data however in second call onwards it gives Uncaught SyntaxError: Unexpected identifier
Can anybody help me out how to implement the use case and how to resolve the issue as i am new to JS.
Thanks
I don't know if this answers your question exactly but, it might get you going. In my experience with jqGrid my automatic grid refreshes looked something like this.
If my HTML looked like this:
<div style="margin: 10px;">
<table id="custGrid"></table>
<div id="ptoolbar"></div>
<div id="pager" class="scroll" style="text-align: center;"></div>
</div>
The the javascript has this:
var grid = $("#custGrid");
jQuery("#custGrid").jqGrid({ ... });
when I refresh the data in the grid I would do something like this:
jQuery("#custGrid").jqGrid('setGridParam', { search: true, postData: { filters: postobj }, page: 1 });
jQuery("#custGrid").trigger("reloadGrid");
The trigger reload tells jqGrid to fetch new data based on the revised grid parameters.
Hope this helps.
setInterval takes a function as first parameter :
setInterval(function () {
$.post("./listStatus.action", {
method: "getStatus",
}, function (data, success) {
if (success == "success") {
var rowsIds = statusGrid.getDataIDs();
console.log("Row Ids:" + rowsIds);
for (var i = 0; i < data.statusList.length; i++) {
var rowData = statusGrid.jqGrid('getRowData', data.statusList[i].rowID);
rowData["Status"] = data.statusList[i].Status;
statusGrid.jqGrid('setRowData', data.statusList[i].rowID, rowData);
}
}
});
}, 5000);
I haven't checked the rest of your code.