I'm using remotipart to upload and upgrade images using ajax, the problem is when I edit an item, ajax updates the data, but remotipart(https://github.com/leppert/remotipart) returns a 'parse error' for image update.
This is how my form looks like:
= form_for(Achievement.new), html: {multipart: true , remote: true} do |f|
= f.text_field :name
= f.text_area :description
= f.file_field :image
= f.submit 'Send'
I'm using a single form to create, edit and delete the 'Achievements'. Here's my js:
constructor: ->
$('.edit_button').click ->
$.ajaxSettings.dataType = "json"
#id = $(this).data('id')
#content = $(this).parent()
#name = $('.form_achievement #name')
#description = $('.form_achievement #description')
#image = $('.form_achievement .avatar img')
#button = $('.form_achievement form input:submit')
#form = $('.form_achievement form')
#Load data to edit on form
$.ajax
type: 'get'
url: "/en/private/achievements/#{#id}/edit/"
success: (data) =>
alert 'edit'
#name.val(data.achievement.name)
#description.val(data.achievement.description)
#stat.html(data.achievement.stat)
#value.val(data.achievement.value)
#image.prop 'src', data.image
#Change method of the form to put and bind event
$.ajaxSettings.dataType = "json"
#form.attr('method','put')
$('.form_achievement form').attr('action', "/en/private/achievements/#{#id}")
$('#achievement_form.accordion .form_achievement').unbind('click', NewAchievement)
#form.bind 'ajax:success', (xhr, data, status) =>
#content.slideUp 'slow', ->
$(this).remove()
alert 'Edit'
#form.bind 'ajax:error', (event, response, error) =>
alert error
#button.bind 'change', => #changeButton()
error: (data) ->
alert 'error'
changeButton: ->
#form.submit()
This solves my problem when I'm trying to do an edit without change image, but when I do an edit trying to upgrade to a new image, returns me a 'parse error'. Can anyone help me?
You're very likely getting a parse error because your response is actually HTML.
The problem is that by default remotipart assumes the server response was JS. You can set the data type when rendering the form, in your case:
form_for(Achievement.new), html: {:'data-type' => :html, :multipart => true, :remote => true}
Or it can be done in JS like so:
#form.bind 'ajax:remotipartSubmit', (event, xhr, settings) =>
settings.dataType = "html *"
#stephencorwin is correct. Remotipart uses a hidden iframe to transport your image. As a result of edit action receiving an html request, your server returns an html response. Remotipart is smart enough to wrap your desired json response in html, and you can use $.parseJSON(data.responseText) to get at the json. But your browser, which is expecting a pure json response, will throw the parserror.
I believe that you can simply ignore this error.
Related
I am a beginner in Rails, but I have done a lot of searching on this and can't seem to find something to help me since I am having difficulty breaking down the problem. I have built a working method that requests information about a book given the ISBN from Amazon and would now like to use it to autofill information about the book after a user enters in the ISBN into a form. Here is my method (which is in my listing.rb model file):
def self.isbn_lookup(val)
request = Vacuum.new('US')
request.configure(
aws_access_key_id: 'access_key_here',
aws_secret_access_key: 'secret_access_key_here',
associate_tag: 'associate_tag_here'
)
response = request.item_lookup(
query: {
'ItemId' => val,
'SearchIndex' => 'Books',
'IdType' => 'ISBN'
},
persistent: true
)
fr = response.to_h #returns complete hash
author = fr.dig("ItemLookupResponse","Items","Item","ItemAttributes","Author")
title = fr.dig("ItemLookupResponse","Items","Item","ItemAttributes","Title")
manufacturer = fr.dig("ItemLookupResponse","Items","Item","ItemAttributes","Manufacturer")
url = fr.dig("ItemLookupResponse","Items","Item","ItemLinks","ItemLink",6,"URL")
return {title: title, author: author, manufacturer: manufacturer, url: url}
end
Here is my controller for now. I am not sure how to make this generic so that the ISBN number relies on what the user enters (it should take in a value given by the user instead of assuming the #isbn instance variable is always set):
def edit
#isbn = Listing.isbn_lookup(1285741552)
end
Here is my _form.html.erb partial where I want to call this ISBN autofill:
<%= form_for(#listing, :html => {class: "form-horizontal" , role: "form"}, method: :get) do |f| %>
<div class="form-group">
<div class="control-label col-sm-2">
<%= f.label :isbn, "ISBN" %>
</div>
<div class="col-sm-8">
<%= f.text_field :isbn, id: "auto-isbn", class: "form-control" , placeholder: "ISBN (10 or 13 digits)", autofocus: true %>
</div>
</div>
...
<% end %>
Finally, here is my JS for what I think should maybe be the start to the AJAX call:
$(document).ready(function() {
$(document).on('keyup','input#auto-isbn',function() {
$.get(this.action, $(this).serialize(), null, "script");
return false;
});
});
How do I make it so that when users put in an ISBN, my app will call the isbn_lookup method and then return the information gathered?
To begin, I would create a lookup path in your routes.rb file. That would look something like:
resources :listings do
collection do
get :lookup
end
end
Which will give you:
lookup_listings GET /listings/lookup(.:format) listings#lookup
Then create the lookup action in your listings_controller.rb, something like:
class ListingsController < ApplicationController
...
def lookup
#isbn_lookup_result = Listing.isbn_lookup(params[:isbn])
render partial: 'isbn_lookup_result'
end
...
end
Naturally, this requires that you have a _isbn_lookup_result.html.erb file that accesses/uses the values from #isbn_lookup_result.
Then, to call this action from your JS, do something like (full disclosure, I use coffeescript, so my plain JS skills are a little rusty):
$(document).ready(function() {
#TIMEOUT = null
$(document).on('keyup','input#auto-isbn',function() {
clearTimeout(#TIMEOUT)
#TIMEOUT = setTimeout(function(){
var ajaxResponse = $.ajax({
url: "listings/lookup",
type: 'GET',
data: {isbn: $('input#auto-isbn').val()}
});
ajaxResponse.success(function(data){
# do stuff with your data response
# perhaps something like:
$('#isbn-lookup-results-container').html(data)
});
}, 500);
});
});
This bit:
clearTimeout(#TIMEOUT)
#TIMEOUT = setTimeout(function(){
...
}, 500);
creates a 1/2 second delay between when your user stops typing and when the ajax function is called. That way, you're not literally doing a lookup on every keyup, only when the user pauses in their typing.
This bit:
var ajaxResponse = $.ajax({
url: "listings/lookup",
type: 'GET',
data: {isbn: $('input#auto-isbn').val()}
});
is the AJAX call. You can see the new listings/lookup path in use. The data: {isbn: $('input#auto-isbn').val()} bit gives you params[:isbn], which is used in the lookup action.
Then, upon success, you use this bit to do something with your response:
ajaxResponse.success(function(data){
# do stuff with your data response
# perhaps something like:
$('#isbn-lookup-results-container').html(data)
});
In this case, data is the HTML that resulted from the render partial: call, so could load it into a div.
Many popular websites have a username field that, usually, turns red or blue as a user types in a new character, based on whether or not the characters entered correspond to an already-existing user.
Say I have the following username field:
<%= f.text_field :username, id:"username" %>
How could that functionality be added to this field?
Instead of checking the username and making request on every key, you can use the blur method to check the user name once the user leaves the username field, or else you need it on every key you can use keyup itself,
Your Javascript,
$( "#username" ).keyup(function() { # you can also try, $( "#username" ).blur(function() {
$.ajax({
url: '<%= check_duplicate_username_path %>', # your own route
type: "GET",
data: { username: $('#username').val() }, // This goes to Controller in params hash, i.e. params[:username]
complete: function() {},
success: function(data, textStatus, xhr) {
// do something with your returned data //
if (data.available == false)
{
$('#username').addClass("error"); // style the class with your required css
}
},
error: function() {
alert("Your Ajax error! message")
}
});
});
The route can be taken as,
get '/check_duplicate_username' => 'users#check_duplicate_username', as: :check_duplicate_username
The controller action can be something like,
def check_duplicate_username
#user = User.where('username = ?',params[:username]).first
if #user.present?
render json: {:success => 0, :message => 'User exists', :user_available => true}
else
render json: {:success => 1, :message => 'User Does not exist', :user_available => false}
end
end
You have to fire ajax request on textbox event.
write ajax function and add new function to you user_controller with GET http method and return suitable response for check availabilty of your username.
Trigger an ajax request while writing on the text box. Like:
$( "#username" ).keyup(function() {
$.ajax({
type: "GET",
url: '<%= username_availability_path %>', # replace by your route
data: {name: $('#username').prop('value')}
});
});
Create a new route on your routes.rb file with type GET. In the method access the typed name using params[:name] and then check if exists or not. Then do whatever you want.
Within a rails 4 app, I am using a link_to to send an upvote on posts via json.
Here is what I have in my posts controller:
def upvote
#post = Post.find(params[:id])
#post.liked_by current_user
respond_to do |format|
format.html {redirect_to :back }
format.json { render json: { count: #post.get_upvotes.size } }
end
end
Here is what I have in my view
<%= link_to like_post_path(post), method: :put, class: 'vote', remote: true, data: { type: :json } do %>
<%= image_tag('vote.png') %>
<%= content_tag :span, post.get_upvotes.size %>
<% end %>
<script>
$(document)
.on('ajax:send', '.vote', function () { $(this).addClass('loading'); })
.on('ajax:complete', '.vote', function () { $(this).removeClass('loading'); })
.on('ajax:error', '.vote', function(e, xhr, status, error) { console.log(status); console.log(error); })
.on('ajax:success', '.vote', function (e, data, status, xhr) {
$(this).find("span").html(data.count);
$(this).find("img").attr("src", '<%= asset_path 'voted.png' %>');
});
</script>
When I click on the link, the vote goes through as a JSON request, I see this in my log:
Processing by PostsController#upvote as JSON
But for some reason, my snipped of javascript is not working. Neither the counter or the icon update. How can I fix this? Does this have to do with turbolinks, does it have to do with where I am placing the javascript?
In Rails you can perform a similar task by having a JavaScript response. Add in your respond_to a format.js similar to format.html then have a view upvote.js.erb that looks like:
(function() {
var postId = "#post-<%= #post.id %>";
$(postId).find(".vote").find("span").text("<%= #post.get_upvotes.size %>");
$(postId).find(".vote").find("img").attr("src", "<%= asset_path "voted.png" %>");
})();
I changed your call to .html to .text since you're not actually setting any HTML inside the element, there is no reason to call .html.
This post also assumes there is some mechanism to identify the post the vote link belongs to (in the example the parent post element has an ID of "post-#" where # is the ID of the post object).
EDIT
Two changes I'd make if I were working on this project. First I would attach the voted.png path to the .vote element as a data attribute. data-voted-image-src="<%= asset_path "voted.png" %>". Next, I would never pass a number in the return as there is no reason to do so. When the vote is clicked you can handle everything on the front end by assuming the request is successful. Which saves all this potential nastiness. While I realize that changing from what you current have to adding the data attribute isn't a huge leap I just find it more semantic than having it in the JavaScript.
The click action on the vote link then becomes:
// Assume all posts have a class 'post'
// I'm also using 'one' because once they vote they don't need to anymore
$(".post").find(".vote").one("click", function(e) {
var count = parseInt($(this).find("span").text()),
votedSrc = $(this).data("voted-image-src");
$(this).find("img").attr("src", votedSrc);
$(this).find("span").text(count + 1);
});
Now no response from the server is necessary, and you can change your JSON response to {success: true} or something simple.
jQuery is the default rails javascript library. The default rails javascript library used to be prototype, so old tutorials/docs use it. This is what the ajax looks like with jQuery:
app/controllers/static_pages_controller.rb:
class StaticPagesController < ApplicationController
def show_link
end
def upvote
respond_to do |format|
format.json {render json: {"count" => "10"} }
end
end
end
app/views/static_pages/show_link.html:
<div>Here is an ajax link:</div>
<%= link_to(
"Click me",
'/static_pages/upvote',
'remote' => true, #Submit request with ajax, and put text/javascript on front of Accept header
data: { type: :json }) #Put application/json on front of Accept header
%>
<div>Upvotes:</div>
<div id="upvotes">3</div>
<script>
$(document).ready( function() {
$(this).ajaxSuccess( function(event, jqXHR, ajaxInfo, data) {
//var js_obj = JSON.parse(jqXHR.responseText);
//$('#upvotes').html(js_obj["count"]);
//Apparently, the fourth argument to the handler, data,
//already contains the js_obj created from parsing the
//json string contained in the response.
$('#upvotes').html(data["count"]);
});
});
</script>
config/routes.rb:
Test1::Application.routes.draw do
get 'static_pages/show_link'
get 'static_pages/upvote'
...
end
url to enter in browser:
http://localhost:3000/static_pages/show_link
See jquery docs here:
http://api.jquery.com/ajaxSuccess/
Response to comment:
You could also do the following in your controller:
def upvote
#upvotes = 2 #Set an #variable to the number of upvotes
respond_to do |format|
format.js {} #By default renders app/views/static_pages/upvote.js.erb
end
end
Then:
app/views/static_pages/upvote.js.erb:
$('#upvotes').html(<%= #upvotes %>)
i have been uploading file with rails with rest-client, but when i change that into ajax,
gives some server error.
FORM code
<%= form_tag({:controller => 'person',:action => 'create'},:multipart => true) do
%>
<%= file_field_tag 'upload', :class => 'upload_name' %>
<%= submit_tag "Upload", :class => "photo_up btn btn-success" %>
<%end%>
AJAX code
$(function(){
$(".photo_up").click(function(){
var filename = $(".upload_name").val();
$.ajax({
url: '/create', //your server side script
data: { upload: filename}, //our data
type: 'POST',
success: function (response) {
}
});
return false;
});
});
CONTROLLER code
file_param = params[:upload]
puts "name os #{file_param}"
show_url = "............."
resource = RestClient::Resource.new show_url, :user => "admin",:password => "admin"
response = resource.post :file => file_param, :multipart => true
Here when i run this code, got the serve error
RestClient::InternalServerError (500 Internal Server Error):, and i know that the server shows the null pointer, in the controller it gives the error in this line response = resource.post :file => file_param, :multipart => true. But its run when in the normal form without ajax.
Unfortunately there is no easy and universal way to send multipart forms via ajax
Look into jQuery ajaxForm plugin.
This makes this thing easy.
http://malsup.com/jquery/form/
I have a form to user login:
<%= form_tag(#action, :method => "post", :name => 'signup' ,:onSubmit => 'return validate();') do %>
<%= label_tag(:user, "Username:") %>
<%= text_field_tag(:user) %>
I want to check if there is the username in the database immediately after :user-field lost focus. I can override this event on the form with javascript, but I can not send Ruby-AJAX request from javascipt code.
Is there any way to check username without adding additional controls (buttons, links) on the form?
You can use some JavaScript (this one written with jQuery) for AJAX cheking:
$(function() {
$('[data-validate]').blur(function() {
$this = $(this);
$.get($this.data('validate'), {
user: $this.val()
}).success(function() {
$this.removeClass('field_with_errors');
}).error(function() {
$this.addClass('field_with_errors');
});
});
});
This JavaScript will look for any fields with attribute data-validate. Then it assings onBlur event handler (focus lost in JavaScript world). On blur handler will send AJAX request to the URL specified in data-validate attribute and pass parameter user with input value.
Next modify your view to add attribute data-validate with validation URL:
<%= text_field_tag(:user, :'data-validate' => '/users/checkname') %>
Next add route:
resources :users do
collection do
get 'checkname'
end
end
And last step create your validation:
class UsersController < ApplicationController
def checkname
if User.where('user = ?', params[:user]).count == 0
render :nothing => true, :status => 200
else
render :nothing => true, :status => 409
end
return
end
#... other controller stuff
end
For what reason can you not send an ajax request from javascript code?
The best way would be to send a GET ajax request when the focus is lost. The get request could then return true or false and your javascript could then reflect this on the page.
I answered this in another post.
It is a friendly way for validating forms if you do not want to write it all from scratch using an existing jquery plugin. Check it out and if you like it let me know!
Check username availability using jquery and Ajax in rails
The solution that #Viacheslav has, works fine and my answer is a combination of his and my own changes (especially JS) part.
We will be using Ajax in order to achieve this.
Lets first create our function in the controller
def checkname
if !User.find_by_display_name(params[:dn])
render json: {status: 200}
else
render json: {status: 409}
end
return
end
and then adding our routes in routes.rb
resources :yourcontroller do
collection do
get 'checkname'
end
end
Now lets gets our hand on the view. Below you'll see the input:
.field
= f.text_field :display_name, onblur: "checkDisplayName.validate(this.value)"
%p.error-name.disp-none username exists
And now by help of JSwe get the magic rolling. Blow JS has few functions. validate does the actually validation. getStatus is our Ajax call to get the status and we use showError & disableSubmitButton to our form a bit more production ready to show errors and disabling the submit button.
var checkDisplayName = {
validate: function(dn){
checkDisplayName.getStatus(dn).then(function(result) {
if (!!result){
if (result.status != 200){
checkDisplayName.disableSubmitButton(true);
checkDisplayName.showError();
} else{
checkDisplayName.disableSubmitButton(false);
}
}
});
return false;
},
getStatus: async (dn) => {
const data = await fetch("/pages/checkname?dn=" + dn)
.then(response => response.json())
.then(json => {
return json;
})
.catch(e => {
return false
});
return data;
},
showError: function() {
let errEl = document.getElementsByClassName('error-name')[0];
if (!!errEl) {
errEl.classList.remove("disp-none");
window.setTimeout(function() { errEl.classList.add("disp-none"); },3500);
}
},
disableSubmitButton: function(status){
let button = document.querySelector('[type="submit"]');
button.disabled = status;
}
};