How to use ajax in Brython - ajax

I am writing a web application using Flask and would like to use browser.ajax functionality in Brython but couldn't find a workable example. It would be very nice if someone demonstrates a short example how to use ajax in Brython. More specifically, how to pass data entered by a user into a textfield to a textarea by clicking submit button. Any help is highly appreciated!
(I am writing this several weeks after I posted the question above). I followed this tutorial on how to implement ajax in Flask (http://runnable.com/UiPhLHanceFYAAAP/how-to-perform-ajax-in-flask-for-python) and tried to replace jquery.ajax by Brython. Unfortunately, I still cannot get it work. Here is my code:
Flask's portion:
#app.route('/')
def index():
return render_template('index.html')
#app.route('/_add_numbers')
def add_numbers():
a = request.args.get('a', 0, type=int)
b = request.args.get('b', 0, type=int)
return jsonify(result=a + b)
Brython/HTML:
<body onload="brython()">
<script type="text/python">
from browser import document as doc
from browser import ajax
def on_complete(req):
if req.status==200 or req.status==0:
doc["txt_area"].html = req.text
else:
doc["txt_area"].html = "error "+req.text
def get(url):
req = ajax.ajax()
a = doc['A'].value
b = doc['B'].value
req.bind('complete',on_complete)
req.open('GET',url,True)
req.set_header('content-type','application/x-www-form-urlencoded')
req.send({"a": a, "b":b})
doc['calculate'].bind('click',lambda ev:get('/_add_numbers'))
</script>
<div class="container">
<div class="header">
<h3 class="text-muted">How To Manage JSON Requests</h3>
</div>
<hr/>
<div>
<p>
<input type="text" id="A" size="5" name="a"> +
<input type="text" id ="B" size="5" name="b"> =
<textarea type="number" class="form-control" id="txt_area" cols="10" rows = '10'></textarea>
<p>calculate server side
</div>
</div>
</body>
</html>
What I get is "result":0. It looks like brython does not send data to the Flask's view function but I don't know how to fix that. So, it would be great if some one could point out what exactly I am doing wrong.

In your example, the Ajax request is sent with the method GET. In this case, the argument of send() is ignored : the data must be sent in the query string appended to the url
The Brython code should be :
def get(url):
req = ajax.ajax()
a = doc['A'].value
b = doc['B'].value
req.bind('complete',on_complete)
# pass the arguments in the query string
req.open('GET',url+"?a=%s&b=%s" %(a, b),True)
req.set_header('content-type','application/x-www-form-urlencoded')
req.send()
If you want to use the POST method, then you can keep the Brython code as is, but the Flask code should be modified : you must specify that the function handles a POST request, and you get the arguments with the attribute "form" instead of "args" :
#app.route('/_add_numbers_post', methods=['POST'])
def add_numbers_post():
a = request.form.get('a', 0, type=int)
b = request.form.get('b', 0, type=int)
return jsonify(result = a+b)

I am working on that - there is nothing ready made, but writing Python code makes it really painless.
I can't post the code I am working on, (and it is far from minimal) - but basically, you write a (Br)Python function to iterate on the HTML, or form DOM, and collect everything that has a "value" in a json-nish structure (a dictionary with nested dicionaries and lists at will) - Them you simply use the browser.ajax object as documented in http://brython.info/doc/en/index.html#, and pass the object with your data as a parameter to the "send" method.
The object data will be URLencoded in the request body. You just have to decode it from there to JSON on the client side.
As an extra hint:
I did not go deep on the question, but I feel like the URLencoding used by default may fail to express everything that is possible in JSON. So imported brython's json module, and do the send like this:
ajax_object.send({"data": json.dumps(json_data)})
This allows me to do this on the client-side:
json_data = json.loads(urllib.unquote(request.body).split(":",1)[-1] )
(the "request.body" is from Pyramid - for flask it is "request.data", but only if the conte-type is not understood by the flask - check How to get data received in Flask request )

Related

Get Data back from Vue Component

Is it possible to get Data back from a vue component?
Laravel blade.php code:
...
<div>
<component1></component1>
</div>
...
In component1 is a selectbox which i need (only the selected item/value) in the blade.php
A vue component, when rendered in the browser, is still valid HTML. If you make sure your component is wrapped in a form element and has a valid input element, and the form can be submitted, the PHP endpoint can consume the form’s data without problems. It could look like this:
Layout/view:
<form method="post" action="/blade.php">
<component1></component1>
<button type="submit">Submit form</button>
</form>
Component (<component1/>):
<fieldset>
<input type="checkbox" name="my_option" id="my_option">
<label for="my_option">I have checked this checkbox</label>
</fieldset>
PHP script (blade.php):
echo $_POST["my_option"] // if checked, should print "on"
If you are looking for a JavaScript centered approach, you may want to serialize the form and fetch the endpoint; it could look similar to this:
import serialize from 'form-serialize';
const formData = serialize(form)
fetch(form.action, { method: 'POST' }, body: JSON.stringify(formData) })
.then(response => {
// update page with happy flow
})
.catch(error => {
// update page with unhappy flow
})
Building from an accessible and standardized basis using proper HTML semantics will likely lead to more understandable code and easier enhancements down the road. Good luck!
(Edit: if you require a complete, working solution to your question, you should post more code, both from the Vue app as well as the PHP script.)

AJAX file upload in Play Framework 2.1 RC1 delivers an empty file

Scala/Play gurus out there.
I'm trying to upload a file using AJAX, in Play 2.1 (RC1). For the client part I'm using eldarion/bootstrap-ajax and everything seems to be fine, except that the uploaded file is empty.
The front-end snippet:
...
<form action="#routes.Campaigns.upload" method="post" class="form ajax replaceable" data-replace=".replaceable">
<input type="file" name="picture">
<p><input class="btn" type="submit"></p>
</form>
...
Note that I had to use the explicit <form> tag instead of the #form helper, due to the fact that the required css class (data-replace) contains a dash, and therefore can not be used as a Symbol. But anyway. The called action in the controller looks like this:
def upload = Action(parse.temporaryFile) {
request =>
Logger.info("Trying to upload a file")
val resultString = try {
val file = new File("/tmp/picture")
request.body.moveTo(file, true)
"file has been uploaded"
} catch {
case e: Exception => "an error has occurred while uploading the file"
}
val jsonResponse = Json.toJson(
Map("html" -> Json.toJson("<p>" + resultString + "</p>")
)
)
Ok(jsonResponse)
}
I'm aware that as my development goes forward the file name should be more intelligently set, but for the moment being, /tmp/picture is for me as good a name as any other one.
The JSON response gets generated (with the "file has been uploaded" message within), and is sent back to the browser as the payload of the 200 response. The JSON is received and correctly used to modify the page (in this case, merely removing the very uploading form).
But the file, although appearing in the right moment and in the right place, is always empty:
larsson:tmp bruno$ ls -l /tmp/picture
-rw-r--r-- 1 bruno staff 0 7 Jan 03:07 /tmp/picture
That's specially strange, in my opinion, because the uploading code which uses a traditional multipart/form-data form, with no AJAX whatsoever, and an Action with parse.multipartFormData as a parameter, instead of parse.temporaryFile, works finely.
Any help will be very appreciated. Thanks in advance.
I don't know bootstrap-ajax, anyway if it hasn't dedicated support for uploading files via AJAX (and I didn't find any info about that possibility in its readme file) it will NOT send files with AJAX.
Reason: In standard JavaScript uploading files with AJAX is not possible due the security limits and there are some techniques to workaround this, mainly using iFrames, however I can't see nothing similar in the code of bootstrap-ajax so probably you need to modify it or use other solution.
Solution: There are some AJAX file uploaders, which works good with HTML5 ie. jQuery File Upload, which offers ajax upload, multi-file uploads, drag file to the drop zone etc.
In general HTML5 supports file uploads better than earlier versions of HTML, so you can build uploader easily without need of using additional plugins, take a look to this topic. As you can see it delivers possibilities to validate some data BEFORE the upload and also offers progress bars.
I'm currently trying to implement something like this and I got a first version working. This is how I do it:
In my Controller I define a method for uploading files. In my case I use Action.async since I save stuff to my MongoDB with reactivemongo. I have removed that code so that it do not complicate this example.
What I do in this example is that I upload a csv file, save it to disk and then produce the first row back as a string to the user. In real life the method produces a list back so that user is able to choose which column represent what an so on.
I use mighty csv for csv parsing. GREAT LIB!
Application:
def upload = Action.async(parse.multipartFormData) {
implicit request =>
val result = uploadForm.bindFromRequest().fold(
errorForm => Future(BadRequest(views.html.index(errorForm))),
form => {
import java.io.File
request.body.file("csvFile").map {
csv =>
val path = current.configuration.getString("csv.job.new.file.path").getOrElse("")
val name = DateTime.now().getMillis + ".csv"
csv.ref.moveTo(new File(path + name))
val settings = CSVReaderSettings.Standard(linesToSkip = form.linesToSkip)
val rows: Iterator[Array[String]] = CSVReader(path + name)(settings)
val firstRow = rows.next()
val test = firstRow match {
case xs if xs.size == 0 || xs.size == 1 => xs.mkString
case xs if xs.size > 1 => xs.mkString(", ")
}
Future(Ok(test))
}.getOrElse(Future(BadRequest("ahadasda")))
}
)
result
}
routes:
POST /upload #controllers.Application.upload
I use # before the controllers because I use DI with guice for my service classes.
Since we will use javascript for uploading we need to define our jsRoutes:
jsRoutes:
def javascriptRoutes = Action {
implicit request =>
import routes.javascript._
Ok(
Routes.javascriptRouter("jsRoutes")(
Application.upload
)
).as("text/javascript")
}
Remember to import in your template where you want to use the routes:
<script type="text/javascript" src="#routes.Application.javascriptRoutes"></script>
<script src="#routes.Assets.at("javascripts/app.js")#Messages("js.version")" type="text/javascript" ></script>
In my view template I have a regular helper form. There is some css style stuff I do to
change the looks and feel of the upload button and file chooser. But the input fields
are there.
index.scala.html:
<div class="csvContainer">
#helper.form(action = routes.Application.upload, 'enctype -> "multipart/form-data", 'id -> "csvUpload") {
#Messages("upload.row.skip")
#inputText(uploadForm("linesToSkip"), 'class -> "hidden")
<div style="position:relative;">
<div id="csvFile" style="position:absolute;">
#Messages("upload.choose")
</div>
<input id="uploadFile" type="file" name="csvFile" style="opacity:0; z-index:1;" onchange="document.getElementById('csvFile').innerHTML = this.value;" />
</div>
<p>
<input type="submit" value="#Messages("upload.submit")">
</p>
}
</div>
In app.js is where the ajax magic happens, remember I have not implemented any validation or cool html5 stuff yet as the progressbar and other handlers, described in besiors link.
I use regular JQuery.
app.js:
$('#uploadFile').change(function(){
var name = $(this).val().split("\\");
console.log(name[2]);
$('#csvFile').text(name[2]);
});
$('#csvFile').click(function(){
$('#uploadFile').click();
});
$("#csvUpload").submit(function(e) {
e.preventDefault();
var formData = new FormData();
formData.append('csvFile', $( '#uploadFile' )[0].files[0]);
formData.append('linesToSkip', $( "#linesToSkip").val());
jsRoutes.controllers.Application.upload().ajax({
data: formData,
processData: false,
contentType: false,
cache: false,
type: 'POST',
success: function(data){
alert(data);
}
});
});
I have removed a lot of code to simplify this example and I hope that I have not forgotten anything. Hope this helps!

How to update a label from a postback in MVC3/Razor

MVC/Razor/Javascript newbie question:
I have a MVC3/Razor form where the use can select a single product from a drop down list.
<div class="editor-label">
Product
</div>
<div class="editor-field">
#Html.DropDownList("ProductID", (IEnumerable<SelectListItem>)ViewBag.Products, "--Select One--")
#Html.ValidationMessageFor(model => model.ProductID)
</div>
What I then want is to display the price of the selected product on a label just below the drop down list (model property name is Amount).
This should be pretty easy, but I am pretty new at Razor, and know almost nothing about Javascript, so I would appreciate any verbose explanations of how do do it, and how it all hangs together.
Add a div/span under the Dropdown .
#Html.DropDownList("ProductID", (IEnumerable<SelectListItem>)ViewBag.Products, "--Select One--")
<div id="itemPrice"></div>
and in your Script, make an ajax call to one of your controller action where you return the price.
$(function(){
$("#ProductId").change(function(){
var val=$(this).val();
$("#itemPrice").load("#Url.Action("GetPrice","Product")", { itemId : val });
});
});
and have a controller action like this in your Product controller
public string GetPrice(int itemId)
{
decimal itemPrice=0.0M;
//using the Id, get the price of the product from your data layer and set that to itemPrice variable.
return itemPrice.ToString();
}
That is it ! Make sure you have jQuery loaded in your page and this will work fine.
EDIT : Include this line in your page to load jQuery library ( If it is not already loaded),
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
The Amount isn't available to your view when the user selects a product (remember the page is rendered on the server, but actually executes on the client; your model isn't available in the page on the client-side). So you would either have to render in a JavaScript array that contains a lookup of the amount based on the product which gets passed down to the client (so it's available via client-side JavaScript), or you would have to make a callback to the server to retrieve this information.
I would use jQuery to do this.
Here's a simple example of what the jQuery/Javascript code might look like if you used an array.
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
$(document).ready(function() {
// This code can easily be built up server side as a string, then
// embedded here using #Html.Raw(Model.NameOfPropertyWithString)
var list = new Array();
list[0] = "";
list[1] = "$1.00";
list[2] = "$1.25";
$("#ProductID").change(displayAmount).keypress(displayAmount);
function displayAmount() {
var amount = list[($(this).prop('selectedIndex'))];
$("#amount").html(amount);
}
});
</script>
<select id="ProductID" name="ProductID">
<option value="" selected>-- Select --</option>
<option value="1">First</option>
<option value="2">Second</option>
</select>
<div id="amount"></div>
You'll want to spend some time looking at the docs for jQuery. You'll end up using it quite a bit. The code basically "selects" the dropdown and attaches handlers to the change and keypress events. When they fire, it calls the displayAmount function. displayAmount() retrieves the selected index, then grabs the value out of the list. Finally it sets the HTML to the amount retrieved.
Instead of the local array, you could call your controller. You would create an action (method) on your controller that returned the value as a JsonResult. You would do a callback using jquery.ajax(). Do some searching here and the jQuery site, I'm sure you'll find a ton of examples on how to do this.

Using MVC3, how to get browsers to explicitly interpret a transferred script as HTML?

In my MVC app, I am returning some Javascript. Howveer, I am using the anti-forgery token on the view, so the rendered result would be
<input name="__RequestVerificationToken" type="hidden" value="E8as+4Ff1u/c/+kuFcNXXCREB5pz5GAfH2krN5RvzURJaHZSApuRc4czZqmoITaKdy0XhN5sFfRzl4ne+wB3PkWOscBWzoIxUk3hGaFwDxRXSbMs8K9IwojEAtV5u57MR7hiSujr6MOTpjjbf5FPaYgO4gmH6lSR9mbSyO2IedI=" />
<script type="text/javascript">
// Here, we ensure that jQuery is loaded then load up the rest of our JS in in order.
ord = Math.random() * 10000000000000000;
...
So there is some HTML to be added to the page then the JS.
The issue is that I get the following notification in Chrome:
Resource interpreted as Script but transferred with MIME type
I need the browser to interpret this as HTML in order to make use of the anti-forgery token.
I have tried putting this on the view:
<%#Page Title="" Language="C#" ContentType="text/xml" %>
Which renders:
<%System.Web.WebPages.DynamicPageDataDictionary`1[System.Object] Title="" Language="C#" ContentType="text/xml" %>
<input name="__RequestVerificationToken" type="hidden"
...
...but the same message persists.
In my controller I have also tried:
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
Byte[] bytes = encoding.GetBytes(page.clientScript);
return new ContentResult
{
ContentType = "text/xml", // also tried text/html
Content = Encoding.UTF8.GetString(bytes),
ContentEncoding = System.Text.Encoding.UTF8
};
Same issue.
-- UPDATE --
This is how I'm invoking the MVC app to return the text:
// used to load scripts on to the client script using a non-blocking asynch request
(function() {
function async_load(){
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'http://myserver/MyAppPath/someids';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}
if (window.attachEvent)
window.attachEvent('onload', async_load);
else
window.addEventListener('load', async_load, false);
})();
If I've understood correctly, you need an MVC action which returns both html and a script tag that can be injected in a page via a <script... include. You also want to render this via an MVC view.
The biggest issue you've missed is that in order to get this content into the calling page, you need to execute document.write from the script - you can't just send back HTML and script in response to the script include - the browser won't understand it, it's expecting javascript only.
There are a few ways to do this - I have written a full suite of ViewContent MVC controller methods, with the same overloads as View which returns the result of a view to a controller action as a string. I can then pass that back as a string literal (useful for html email generation) but also to a javascript encoder.
In this case, you don't need to be so generalist. We can leverage Darin Dimitrov's answer to this SO: Embed MVC Partial View into a document.write JS call and split your view into a View and a partial. The view writes the document.write() skeleton, and the partial view renders the dynamic html you want to be injected into the page. It's unclear if you're using the Anti Forgery Token in the main view which will call the script (in which case it should be rendered as part of the view that it returns) or if you're actually hard-coding it in the script. The second should definitely not be used but I'm writing this answer as if it is, because that appears to be what you want.
First, your partial view (let's call it Fragment.cshtml, put it in ~/Views/Shared)
<input name="__RequestVerificationToken"
type="hidden"value="[ommitted]" />
<script type="text/javascript">
// Here, we ensure that jQuery is loaded then load up the rest of our JS in in order.
ord = Math.random() * 10000000000000000;
...
Second, the host view, called SomeIds.cshtml
#{ Response.ContentType = "text/javascript"; }
document.write('#Html.Raw(HttpUtility.JavaScriptStringEncode(Html.Partial("~/Views/Shared/Fragment").ToHtmlString()))')
Now this view returns a document.write call that injects the HTML returned by the Fragment.cshtml into the page that includes the script.
Are you returning a PartialView that has all of the markup rendered?
Create a PartialView with your (form and script includes) and in your Controller:
public ActionResult Index(Models.MyModel model)
{
// validate the model if needed
return PartialView("[My PartialView Name]", model);
}
You could put your scripts in separate files, and add the [script src] tags in the PartialView.

Ruby - Sinatra: Asynchronously calling method with form input as parameter

I am currently trying to build a little widget that will retrieve a list of artists based on a username.
The Ruby method requires a username parameter after which an API call is made that retrieves the actual array of strings.
The web page has an input field where the user can fill out his/her username. My goal is to immediately call the ruby method and display the list of artists. My problem is being able to use the actual form input as the parameter. I figured this would be relatively easy with params[:user], in the same way it's done in a Sinatra post method. Alas, turns out it isn't.
I tried both a JS approach and directly calling the method after :onkeyup.
Javascript:
userChanged = function() {
var user = document.getElementById("username");
if (user.value.length != 0){
artists = #{RFCore::get_artists(:name => params[:user]).to_json};
art_list.innerHTML = artists
};
};
:onkeyup
:onkeyup => "art_list.innerHTML = #{RFCore::get_artists(:name => params[:user])[0]}"
I have substituted params[:user] with all variations I could think of such as "#{user}" and user.
The errors returned are undefined method []' for params[:user] and undefined local variable or methoduser' for "#{user}" and user.
Perhaps there is an easy solution to this; but the feeling is starting to creep up on me my approach is wrong to begin with. I am open to any other way of achieving this.
As far as I understood, you are generating that JavaScript dynamically. So when your Ruby code produces it, it evaluates that RFCore::get_artists expression when you are generating the JavaScript code, not when the user interacts with the web page.
If that's the case, I recommend:
Use jQuery. It makes your life much easier.
When there's some user interaction (e.g., a key press), use Ajax to communicate with your server to get back a list of artists.
Here is a small Sinatra application that demonstrates this approach:
require 'sinatra'
get '/' do
<<html
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
function userChanged()
{
$.get('/get-artists',
{username: $('#username').val()},
function(data){
$('#artists').html(data);
});
}
</script>
User: <input id="username" type="text">
<button onclick="userChanged();">Look up</button>
<div id="artists"/>
html
end
get '/get-artists' do
"Generate here list for user #{params[:username]}"
end
Please notice that the above code is just an example. The HTML generated is all wrong, no template language is being used, etc.

Resources