Rails+Backbone+Faye messaging, how to instantiate model and remove its element from DOM for all subscribers? - ruby-on-rails-3

I am using Rails + Backbone + Faye to make a sample chat application.
I'm currently able to use Faye's messaging capabilities to write to the DOM on a create event, though I'm not actually instantiating a backbone model. Ala Ryan Bates' tutorial I'm just calling inside of
create.js.erb
<% broadcast "/messages/new" do %>
$("#chats-table").append("<%= escape_javascript render :partial => "chat", :locals => { :chat => #chat } %>");
<% end %>
And publishing it in another javascript:
faye.subscribe("/messages/new", function(data) {
eval(data);
});
I'd like to refactor this a bit and leverage backbone's models. A good use case would be the delete method.
My chat model is bound to a click event, delete which calls:
model.destroy();
this.remove();
Backbone will call the delete method and put a delete request to /entity/id
That also dispatches rails' /views/delete.js.erb'.
In there I call a helper method which publishes a message with Ruby code.
<% broadcast "/messages/delete" do %>
<%= #chat.to_json.html_safe; %>
<% end %>
listener
var faye = new Faye.Client('http://0.0.0.0:9292/faye');
faye.subscribe("/messages/delete", function(data) {
});
Here, I was wondering if i could instantiate the deleted backbone model somehow so I could push that event onto everybody's screen and remove it from the DOM. Basically, I would like to call this.remove(); inside the faye client instead of in the chat model. Is this even possible?

Well, you should do remove on the model and let the UI listen for the event and refresh itself. Once the UI reflects the model changes you are golden.
The problem you have here is that Backbone collections/models are not an identity map. So the model object you are dealing with in the view is not the same you will instantiate and remove from the faye callback. If your messages collection is globally accessible, then I suggest you get the instance from there are remove it.

Related

How can I submit a form on input change with Turbo Streams?

I have a form I want to submit automatically whenever any input field is changed. I am using Turbo Streams, and if I use onchange: "this.form.submit()" it isn't captured by Turbo Streams and Rails uses a standard HTML response. It works fine when clicking the submit button. How can I work around this?
There is a discussion on the hotwire forum, where Mark Godwin figured out why form.submit() isn't working with turbo:
Turbo intercepts form submission events, but weirdly, the JS formElement.submit() method does not trigger the submit event.
And Jacob Daddario figures out that you can use form.requestSubmit() instead:
It turns out that the turbo-stream mechanism listens for form submission events, and for some reason the submit() function does not emit a form submission event. That means that it’ll bring back a normal HTML response. That said, it looks like there’s another method, requestSubmit() which does issue a submit event.
So you can change your code slightly, and use requestSubmit() if a browser supports it, and use submit() if not:
onchange: "this.form.requestSubmit ? this.form.requestSubmit() : this.form.submit()"
Update:
As BenKoshy pointed out, in Turbo 7.1.0, a polyfill was added so you can use form.requestSubmit() without checking for browser support, so you can add this to your input field:
onchange: "this.form.requestSubmit()"
I need to implement this for an app with lots of forms. I wound up using Stimulus. Below is the whole controller:
import { Controller } from "stimulus"
const _ = require("lodash")
export default class extends Controller {
connect() {
let that = this;
that.element.addEventListener('change', _.debounce(that.handleChange, 500))
}
handleChange(event) {
event.preventDefault()
// event.target.name // => "user[answer]"
// event.target.value // => <user input string>
event.target.form.requestSubmit()
}
}
and here it's used in a form with a single text input. NOTE the controller is attached to the form, not to the inputs.
<%= turbo_frame_tag dom_id(form_model) do %>
<%= form_with model: form_model,
format: :turbo_stream,
html: { data: { controller: "buttonless-form" } } do |f| %>
<%= f.hidden_field :question_id, value: question.id %>
<%= f.text_field :answer_value, class: "input shadow wide", placeholder: "Enter your answer here" %>
<% end %>
<div id=<%= "question_#{question.id}_output" %>>
<p> <!-- feedback to the user shows up here via Turbo -->
</div>
<% end %> <!-- end turbo frame -->

Tracking of vehicles in Rails via geocoding and plotting on a map

I have a Rails 3.2.19 app that I'd like to track vehicles (Units) with using geocoding and plotting on Google Maps.
I came up with a way to get coordinates by using the taip_parser gem and creating a rake task that constantly listens for inbound taip data, parses the latitude and longitude and updates the vehicle model's latitude and longitude fields. From there I was able to plot vehicle locations using the gmaps4rails gem. The limitation of this is that you have to use a specific 3G/4G modem that speaks in TAIP to send the lat/long to the Rails server so the rake task can parse it.
What I'd like to do is to negate having to use these expensive 3G/4G modems and instead pull the coordinates from a mobile device located in the vehicle (currently an iPad).
So my thoughts are to use the HTML5 Geolocation feature in the browser to obtain the latitude and longitude of the unit and somehow store that into the Unit's latitude/longitude database fields upon page/partial refresh which happens currently via Ajax.
This would break the dependence on the the existing devices and allow any mobile GPS enabled device to be compatible with this feature.
Currently in my app I have gmaps4rails to plot the units using my rake task and taip data parsing and also geocoder which I'm testing to geocode addresses as they are created for another purpose.
My questions are:
Can someone provide an example of how to use the HTML5 geolocation feature in a rails view? Once HTML5 geolocation is enabled in the view, how to get the latitude and longitude into the respective models latitude and longitude fields? Perhaps an example iteration of multiple objects' latitude longitude using gmaps4rails
If my question is too vague or convoluted please let me know so I can edit it and make things more clear as to what I'm trying to do.
I was able to get this working by using some ajax and a custom controller action in my Rails app. Using Ajax and a hidden form, I pull the coordinates using HTML5, populate the form with the values, and to an ajax post submit.
See below for the code:
controller
def update_gps
#unit = current_user.unit
if #unit.update_attributes(params[:unit])
respond_to do |format|
format.html {redirect_to mdt_path}
format.js { render "index"}
end
end
end
index.html.erb
<div class="hidden">
<%= form_for(#unit, :url => update_gps_mdt_index_path, :html => {:method => :post}) do |f| %>
<%= f.text_field :latitude %>
<%= f.text_field :longitude %>
<%= f.button :submit %>
<% end %>
</div>
<script>
$(function() {
setInterval(function(){
$.getScript("/mdt");
}, 10000);
});
function updateCurrentLocation(){
navigator.geolocation.getCurrentPosition(function(position) {
var c = position.coords
var location = c.latitude +", "+ c.longitude
console.log(location)
var $updateLocationForm = $(".edit_unit")
$("#unit_latitude").val(c.latitude)
$("#unit_longitude").val( c.longitude)
var data = $(".edit_unit").serialize();
console.log(data)
$.ajax({
type: "POST",
url: $(".edit_unit").attr("action"),
data: data,
success: function(data) {
console.log('working: '+data);
},
error: function(msg) {
console.log('not working '+msg);
}
});
});
}
setInterval(updateCurrentLocation, 10000)
</script>

Press submit on Ajax Form from javascript / jquery timer

I would like to automatically click the submit button of an Ajax enabled form, so that the user does not have to click the button (but can optionally).
Right now, I'm working on the first boundary, which is to call the form from Javascript, so that at the very least, once i build my timer, I will have this part figured out.
I've tried many ways to do this, and NONE work. Please keep in mind that this is an ASP.NET MVC 4 Mobile application (which uses jquery.mobile) but I do have the jquery.mobile ajax disabled so that my button works at all (creating manual ajax based forms with updating divs, does not work in a jquery.mobile app because it hooks on the submit of all ajax forms).
So my current button works fine, I just can't seem to fire it programmatically.
I have my form:
<% using (Ajax.BeginForm("SendLocation", null, new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "result", HttpMethod = "POST" }, new { #id = "locationForm" }))
{ %>
<ul data-role="listview" data-inset="true">
<li data-role="list-divider">Navigation</li>
<li><%: Html.ActionLink("About", "About", "Home")%></li>
<li><%: Html.ActionLink("Support", "Support", "Home")%></li>
<li data-role="list-divider">Location</li>
<%: Html.HiddenFor(model => model.GPSLongitude)%>
<%: Html.HiddenFor(model => model.GPSLatitude)%>
<li><input type="submit" id="submitButton" value="Send" /></li>
</ul>
<% } %>
I have tried to do this in javascript:
$.ajax({
type: "POST",
url: action,
success: function () {
alert('success');
}
});
And I do get the server code firing that normally would. However, the DIV is not updated and also, the model was not intact either (it existed with all internal values null, so i assume newly instantiated).
I have also tried different ways to fire the form:
var form = $('#locationForm', $('#myForm'));
if (form == null) {
alert('could not find form');
} else {
alert('firigin on form');
form.submit(function (event) { eval($(this).attr("onsubmit")); return false; });
form.submit();
}
This did not work either:
var f = $('#locationForm', $('#myForm'));
var action = f.attr("action");
var data = f.attr("data");
$.post(action, data, function() { alert('back'); });
Which were all ways to do this that I found throughout the web.
None of them worked to fire the form and have it work the way it would normally as if a user had pressed the submit button themselves. Of course, once this fails, if I hit my submit button, it works perfectly...
Using Chrome Developer Tools, I found that the $.ajax call needs to have valid data before it will even attempt to function.
I was getting a silent Internal 500 Error on the post. But of course because of AJAX it was silent and the controller was not firing because it didn't get past IIS.
So I found out that the data I was sending, saying its JSON, was not and the .serialize() does not use JSON formatting. I tried to incorporate the JSON Javascript libraries to convert the object into JavaScript, however, this does not work either, because the Data Model object (or the form object) seems to not be compatible with those libraries. I would get errors in the JavaScript console and those libraries would crash when trying.
I decided to actually just pass the object I want manually:
var encoded = '{ GPSLongitude: ' + $('#GPSLongitude', $('#myForm')).val() + ',GPSLatitude: ' + $('#GPSLatitude', $('#myForm')).val() + '}';
Which passed the hidden fields i wanted to send (GPS LON/LAT) to the controller, and the model was intact in the controller call!
Now, for anyone that is reading this answer. the actual AJAX update process that is supposed to update the view, failed to work. Although for my purpose, I did not actually need the view to update correctly. Eventhough a partial view is returned, the special AJAX call seems to break the linkage between the form's div to update.
However, since the data was passed to the controller intact, this basically passed the GPS data that I needed to the server which was my ultimate goal.
make sure you are including the proper js libraries.
you need. jquery.js, jquery.unobtrusive-ajax.js
make sure unobtrusivejavascriptenabled = true in the web.confg
<appSettings>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
please try $('#locationForm').submit();
does it give error message?
if you're using i.e. you can look use the develper tools to look at network traffic to make sure nothing is sent.

How to render js rails 3

I have a vote model with "like", "dislike" actions. I have a route for each of these actions. When I call the actions, I'm returning a json response. My problem is that first, I need to figure out how to send the query to my like/dislike actions. I need to access ruby/rails variables from my javascript (I'm sending an ajax request using jquery's $.getJSON), so that for example I can create the request for the correct item. Help much appreciated.
A popular technique is to attach data to the dom. For example (pseudocode follows):
<% items.each do |item| %>
<div class="like_button" data-item-id="<%= item.id %>">Like</div>
<% end %>
and in the JavaScript:
$(".like_button").on("click", function() {
var item_id = $(this).data('item-id'); // from the dom
// construct Ajax request for item_id
});

Rails 3, how to prevent ajax remote calls (jquery) with a link_to and :remote => true with JS

So I have a simple ajax call to a page:
= link_to 'click me', my_path, :onclick => "if ($('#go').val() == "ok") { alert('going'); } else { return false; }", :remote => true do
This works just fine, I see the alert only when my field with id "go" has ok in there... but the issue is that the remote action triggers every time no matter what.
If this was not a remote link it would work just fine, not going through with the link, but it does not seem to behave the same way with a :remote => true ?
How can I achieve the expected result ?
Thanks,
ALex
The issue here is that the Rails UJS driver will see the data-remote and then perform the action because of a function like this in rails.js so perhaps try setting the property om your link inside the JS and remove the :remote => true. That might work however I dont know if rails.js would bind to that correctly or not.
Also, consider placing this JS in the application.js once you're done debugging just so you dont have inline JS all over your controllers.