How to pass parameters in axios url? - ruby

I am trying to use Axios in my Ruby Sinatra Project.
There is a url defined in my ruby file.
post '/follow/:id' do
# do something
end
In my erb file, I am trying to pass the variable in view to my inline script.
<body>
<div id="follow_element">
<button v-on:click="followUser">Follow</button>
</div>
<script>
window.addEventListener('load', function(){
const app = new Vue({
el: "#follow_element",
methods:{
followUser(){
axios.post("/follow", {
params: {
id: <%= #user.id %>
}
})
}
}
})
})
</script>
</body>
What I want is if the user click the button, the app would hit the /follow/:id url to update result. However, I met two problems.
First, the <%= #user.id %> doesn't work (#user is a variable available in this erb). The second is the app hits the '/follow' endpoint, instead of '/follow/:id' endpoint.
Could you please give me some suggestions? Thanks very much.

Note that as you wrote this:
post '/follow/:id' do
# do something
end
You'll now accept POST requests like: /follow/oneRandomId, follow/124184965 and so on...
Right here, you are making a request to /follow, passing POST params that is not in url. This will not work:
axios.post("/follow", {
params: {
id: <%= #user.id %>
}
})
In order to fix it, you can generate an URL with the available #user.id and make the POST request, like this:
axios.post("/follow/<%= #user.id %>")
Or, if you want to pass some data to the user (POST data, not URL params), you can do this:
axios.post("/follow/<%= #user.id %>", {
name: 'A beautiful name to insert to an user with that ID'
})
If you're still getting stuck, you can also use axios(config), that I think a bit clearer about what it is doing, from axios README page:
axios({
method: 'post',
url: "/follow/<%= #user.id %>",
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});

Related

Rails 6 action cable chat app not working in firefox

I made a Rails 6 app and added a private messaging feature in it using action cable. The chat works fine in chrome and edge but crashes in firefox. Upon investigation in firefox, I think whenever the 'send' button in the create message form is clicked, it seems it reloads the page, disconnecting the current subscription. It says in the terminal that the async job is performed successfully before it gets disconnected. The data (in received(data)) doesn't get console logged in the browser.
I thought I just need to prevent the page to reload once the 'send' button is clicked so I tried placing preventDefault in submit listener in both application.js and conversation_channel.js but didn't work. Also added onclick: false in the submit tag in message form but didn't work also. It's maybe my theory is wrong or maybe I executed the solution wrong.
Please tell me if you need more snippets of my code:
// this is app/javascript/channels/conversation_channel.js
import consumer from "./consumer"
document.addEventListener('turbolinks:load', () => {
const conversation_element = document.getElementById('conversation-id');
const conversation_id = Number(conversation_element.getAttribute('data-conversation-id'));
const input_box = document.getElementById('message-input-box');
const send_button = document.getElementById('send-btn');
// for terminating other subscriptions when connected to a new subscription
consumer.subscriptions.subscriptions.forEach(subs => {
consumer.subscriptions.remove(subs);
});
consumer.subscriptions.create({ channel: "ConversationChannel", conversation_id: conversation_id }, {
connected() {
// Called when the subscription is ready for use on the server
console.log('Connected to conversation id: ' + conversation_id);
send_button.addEventListener('submit', function(e) { e.preventDefault(); });
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
// Called when there's incoming data on the websocket for this channel
console.log(data);
const el_user_id = document.getElementById('user-id');
const user_id = Number(el_user_id.getAttribute('data-user-id'));
let html;
user_id === data.message.user_id ? html = data.own_message : html = data.not_own_message;
const messageContainer = document.getElementById('messages-container');
messageContainer.innerHTML += html;
send_button.disabled = false;
input_box.value = '';
}
});
});
this is where the user creates a message: app/views/messages/_form.html.erb
<%= form_with scope: :message, url: item_conversation_messages_path(#conversation.item.id, #conversation.id), local: true, remote: true do |f| %>
<div class="form-inline">
<%= f.hidden_field :conversation_id, value: #conversation.id %>
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.text_field :content, placeholder: 'Type your message here...', class: 'form-control mr-2', id: 'message-input-box' %>
<%= f.submit 'Send', id: 'send-btn', class: 'btn btn-primary' %>
</div>
<% end %>
It was pointed out by user even_progression in my reddit post https://www.reddit.com/r/rails/comments/ol9ygj/rails_6_action_cable_chat_app_not_working_in/ that I should check the local and remote options in the message form. I set local to false and remote to true and now it works. Idk yet why but I'm about to find out.

broadcast to others and dont broadcast to current user are not working

In my TaskList.vue I have the code below:
<template>
<div>
<ul>
<li v-for="task in tasks" v-text="task"></li>
</ul>
<input type="text" v-model="newTask" #blur="addTask">
</div>
</template>
<script>
export default {
data(){
return{
tasks: [],
newTask: ''
}
},
created(){
axios.get('tasks')
.then(
response => (this.tasks = response.data)
);
Echo.channel('task').listen('TaskCreated', (data) => {
this.tasks.push(data.task.body);
});
},
methods:{
addTask(){
axios.post('tasks', { body: this.newTask })
this.tasks.push(this.newTask);
this.newTask = '';
}
}
}
</script>
When I hit the axios.post('tasks') end point, I got duplicate result in my current tab that i input the value and the another tab got only 1 value which is correct.
To avoid this, I tried to use
broadcast(new TaskCreated($task))->toOthers();
OR
I put $this->dontBroadcastToCurrentUser() in the construct of my TaskCreated.php
However, both methods are not working. Any idea why?
The image below is from network tab of mine. One of it is pending, is that what caused the problem?
https://ibb.co/jpnsnx (sorry I couldn't post image as it needs more reputation)
I solved this issue on my Laravel Spark project by manually sending the X-Socket-Id with the axios post.
The documentation says the header is added automatically if you're using vue and axios, but for me (because of spark?) it was not.
Below is an example to show how I manually added the X-Socket-Id header to an axios post using the active socketId from Laravel Echo:
axios({
method: 'post',
url: '/api/post/' + this.post.id + '/comment',
data: {
body: this.body
},
headers: {
"X-Socket-Id": Echo.socketId(),
}
})
Laravel looks for the header X-Socket-ID when using $this->dontBroadcastToCurrentUser(); Through this ID, Laravel identifies which user (current user) to exclude from the event.
You can add an interceptor for requests in which you can add the id of your socket instance to the headers of each of your requests:
/**
* Register an Axios HTTP interceptor to add the X-Socket-ID header.
*/
Axios.interceptors.request.use((config) => {
config.headers['X-Socket-ID'] = window.Echo.socketId() // Echo instance
// the function socketId () returns the id of the socket connection
return config
})
window.axios.defaults.headers.common['X-Socket-Id'] = window.Echo.socketId();
this one work for me..!!

rails 4 , Ajax request , can I get a response with html AND a json objects?

I am currently requisition an html partial to be inserted into a modal
$('#user-addTool').on('click', function(e) {
$('#userModal div.modal-content').html('');
var ajaxUrl = "http://" + window.location.host + "/users/new";
e.preventDefault();
e.stopPropagation();
$.get(ajaxUrl, function(data){
$('#userModal div.modal-content').html(data);
initGroupSelector();
initRoleSelector();
$('#role-selector').multiselect('disable');
}, "html");
$('#userModal').modal('show');
});
served by a rails app :
# GET /users/new
# GET /users/new.json
def new
#user = User.new
#user.profile = Profile.new
authorize #user
render partial: 'users/form', layout: false
end
I wonder if I can return both, the partial form html AND a son object ( groups and roles to be used in the client js script ?
thanks for feedback
UPDATE 1 --
Can I send a JSON response this way ?
format.json {
#response = {
"html": { "form": <%= (render partial: 'users/form').to_json.html_safe %> },
"data": { "groups": <%= #groups %> , "roles": <%= #roles %> }
}
render(json: #response, status: :success )
}
In which #group is an Array of Arrays and #roles a Hash of Hashes ?
thanks for advices

BackboneJS + Codeigniter pushState true not working

So I have this Backbone App where I use Codeigniter for the Backend. For some reason, pushState:true does not work.
So, my main.js of my backbone app has this:
Backbone.history.start({ pushState: true, root: App.ROOT });
My app.js has this:
var App = {
ROOT: '/projects/mdk/'
};
and my navigation module, which renders the menulinks, each item has this:
this.insertView(new ItemView({
model: new Navigation.ItemModel({
href: App.ROOT + 'home',
class: 'home',
triggers: 'home',
route: this.route
})
}));
and the model for it:
Navigation.ItemModel = Backbone.Model.extend({
defaults: {
href: '',
text: '',
triggers: [],
route: ''
}
});
All I get from this is "Page not found"...
Add: When I in the view change it to href:'#news' - it works, but it dont really makes sense...
Anyone who knows the issue here?
From the documentation (http://backbonejs.org/#History):
Note that using real URLs requires your web server to be able to
correctly render those pages, so back-end changes are required as
well. For example, if you have a route of /documents/100, your web
server must be able to serve that page, if the browser visits that URL
directly.
The problem is that your server isn't responding to whatever URL your app is on. For every URL that your Backbone app can reach, your server MUST return a valid HTML page (contianing your Backbone app).
ok I found a solution by myself:
I made this hack:
$(document).on('click', 'a:not([data-bypass])', function (evt) {
var href = $(this).attr('href');
if (href && href.indexOf('#') === 0) {
evt.preventDefault();
Backbone.history.navigate(href, true);
}
});
and then I made:
href: '#home',
That solved the problem, now evereythings runs fluently..

ruby, sinatra, Plivo API: render API call as HTML

I am making an API call to Plivo to list available telephone numbers.
I can access the returned response and print the desired elements in my terminal BUT I do not know how to render them as HTML on my web page. This is my problem.
In the terminal, the response to a successful call is:
{"api_id"=>"23f1f0f0-0808-11e3-a442-22000ac6194a",
"meta"=>
{"limit"=>1, "next"=>nil, "offset"=>0, "previous"=>nil, "total_count"=>1},
"objects"=>
[{"group_id"=>"23928520636825",
"number_type"=>"local",
"prefix"=>"646",
"region"=>"New York, UNITED STATES",
"rental_rate"=>"0.80000",
"resource_uri"=>
"/v1/Account/MAZDQ1ZJIYMDZKMMZKYM/AvailableNumberGroup/23928520636825/",
"setup_rate"=>"0.00000",
"sms_enabled"=>true,
"sms_rate"=>"0.00800",
"stock"=>50,
"voice_enabled"=>true,
"voice_rate"=>"0.00900"}]}
"0.00900"
New York, UNITED STATES
646
The Ajax script which generates the response is:
$(".localsearch").click(function() {
var country_iso = $("#local").val();
var region = $("#region").val();
var prefix = $("#prefix").val();
$.ajax({
type: "GET",
url: "/local/data",
data: { 'country_iso' : country_iso, 'region' : region, 'prefix' : prefix },
success: function(data) {
alert(data)
},
});
});
The alert doesn't help and just shows the entire page.
The ruby code is:
get '/local/data' do
country_iso = params[:country_iso]
region = params[:region]
prefix = params[:prefix]
p = RestAPI.new(AUTH_ID, AUTH_TOKEN)
params = {'country_iso' => country_iso, 'region' => region, 'prefix' => prefix, 'limit' => '1'}
response = p.get_number_group(params)
obj = response.last
pp response.last
#region = obj["objects"][0]["region"]
puts #region
#prefix = obj["objects"][0]["prefix"]
puts #prefix
erb :search
end
So, sorry it's long and to summarize, how do I extract elements from the API response and print them as HTML? Many thanks in advance.
In the view I have tried:
<%= #region %> and <%= obj['region'] %> and <%= obj['objects][0]['region'] %>and none of them work.
Yours is a perfect use case of of rendering a partial through a ajax call.
So what you can do is:
Make your Sinatra action return html using rails like render partial functionality like this
http://steve.dynedge.co.uk/2010/04/14/render-rails-style-partials-in-sinatra/
(to get rails like partial functionality in sinatra you can use this gem also https://rubygems.org/gems/sinatra-partial )
Now since now your sinatra action returns a valid html, in your ajax success function you can just write:
$(".localsearch").click(function() {
var country_iso = $("#local").val();
var region = $("#region").val();
var prefix = $("#prefix").val();
$.ajax({
type: "GET",
url: "/local/data",
data: { 'country_iso' : country_iso, 'region' : region, 'prefix' : prefix },
success: function(data) {
$('unique_identifier_of_your_partial_on_the_html_dom').html(response)
}
});
});
another example of rendering partial in sinatra:
Ruby w/ Sinatra: Could I have an example of a jQuery AJAX request?
extract out the html that you want to populate with the response from this ajax call into a a separate erb file lets say , _my_response_partial.html.erb
now suppose this is your search.html.erb file.
#something somrthing
<%= erb(:my_response_partial, locals => {:region => #region, :prefix => #prefix},:layout => false) %> #pass whatever data you want to pass to a partial using locales
# something something
and in your get action replace the last line with:
erb(:my_response_partial, locals => {:region => #region, :prefix => #prefix},:layout => false)
By this way your action will just return the html required to populate that partial.

Resources