I have a backbone application which, upon load, needs to fetch data from four different collections (Rails-->JSON back end).
That's four hits to the server, and I'm guessing there's a better way.
I started out by trying to pass Rails to_json() of the query results into the router initialization in the Rails view such as:
<script type="text/javascript">
$(function() {
window.router = new Backbonedemo.Routers.CalendarsRouter({calendars: [], tasks: <%= #tasks %>});
Backbone.history.start();
});
</script>
but that brought no joy.
So, what's the correct way to run the equivalent of fetch() at startup, without having to hit JSON for each collection I want to collect?
Check out the rabl gem. It allows you to customize your json response to a much greater degree than regular to_json will allow.
Here's a basic way to set up a project where you need to deliver a load of JSON up front:
First, set up your controller to pull data on page load, for examlpe localhost:3000/home would look in the home controller index:
class HomeController < ApplicationController
def index
#user = current_user
render 'user.json' # this line is not actually required most of the time when using backbone and proper ajax/json requests
end
end
Next, set up a rabl template, this takes the place of a view or a partial, and returns JSON to your client. I'm actually going to use a partial, to make loading into the home/index.html view nice and easy:
# views/home/_user.json.rabl
object #user
attributes :id, :first_name, :last_name, :birthdate, :gender, :nickname, :email
node(:avatar_thumb_url) { |u| u.avatar.url :thumb }
node(:roles) { |u| u.roles }
node(:name) { |u| "#{u.first_name} #{u.last_name}".strip }
node(:errors) { |u| u.errors.to_hash if u.errors.present? }
child :awesome_calendars => :calendars do
attributes :id, :date, :description
child :events do
attributes :title, :description
end
end
That's some relatively fancy rabl that will deliver a bunch of json, including a related set of records, all in one JSON object.
In your html view that loads up backbone, you need to pass the controller's object to the partial:
# views/home/index.html.erb
<script type='text/javascript'>
$(function() {
window.router = new Backbonedemo.Routers.CalendarsRouter(<%= render('user.json.rabl', user: #user).html_safe -%>);
Backbone.history.start();
});
</script>
To recap:
controller renders regular html.erb view (the one that starts up backbone)
that view also renders a partial--this partial is a rabl template that returns strictly JSON
backbone takes that JSON and does whatever you want with it.
The beauty of this is that you can set up json.rabl responses for any of your controller actions and have them return a variety of json stuff, all easily controllable. The thing I did above is the "difficult" part where you want to load up stuff from many tables into a single JSON call on your first page load--avoiding multiple AJAX/backbone fetch requests.
Make sense? I hope so... : / let me know if anything is unclear.
I don't know Rails, but see the "bootstrap" example in the Backbone docs:
<script>
Accounts.reset(<%= #accounts.to_json %>);
Projects.reset(<%= #projects.to_json(:collaborators => true) %>);
</script>
Generally, I think you need to create the collection objects, then reset() them with inline JSON data.
Related
I am using the FileStack API and the file picker gem (https://github.com/Ink/filepicker-rails). I have an Attachment model that has a :title as a string. When a file is uploaded, the URL from the FilePicker API is stored as the :title. But the gem has a onchange method that returns an event variable as a JSON object that contains attributes of the file. I use JavaScript to access those attributes but I want to find a way in Rails to store those attributes, accessed via JavaScript, in a Model so that I can access it through the rest of the Rails app.
<%= filepicker_js_include_tag %>
<%= simple_form_for(#attachment) do |f| %>
<%= f.filepicker_field :title, onchange: 'onUpload(event)' %>
<%= f.submit %>
<% end %>
<script>
function onUpload(event) {
console.log(event);
var name = event.fpfile.filename;
console.log(name);
}
</script>
Update:
So after looking into your solution and googling around I am using ajax to send the data via routes to the controller. Below is my updated Javascript as well as the route and controller. When I render and inspect the #foo instance variable it is nil. So my data isn't getting passed properly. Furthermore, this whole process from the firing of the Javascript function to displaying the index view is now very very slow. I think I have the right idea after viewing your solution and doing more digging but I'm missing something and/or overcomplicating this. Any advice would be much appreciated.
<script>
function onUpload(event) {
var name = event.fpfile.filename;
jQuery.ajax({
data : { data_value: event },
type: 'post',
url: "/attachment/index"
});
}
</script>
Route
post 'attachments/' => 'attachment#index'
Controller
def index
#attachments = Attachment.all
#foo = params[:data_value]
end
View (returns nil)
<%= raise #foo.inspect %>
If you're using Postgres 9.3 or above you should consider using the hstore module and creating a JSON column. In a migration you can do:
add_column :your_model, :your_attribute, :json
And then you can just update YourModel.your_attribute => {'your': 'JSON here'}
Docs here: http://edgeguides.rubyonrails.org/active_record_postgresql.html#json
If you're using MySQL it's tricky, but doable. You have to create a text column and save the JSON as a string, and parse it every time you interact with it. Postgres is definitely better at handling JSON. I realize that this answer relies on an assumption, so if you're not using one of the two data stores mentioned, let me know and I'll pull it down.
I want to start using websockets. I read all the docs and understand everything, but it leaves out something I need: How to pass information from view to JS?
I need to pass data from javascript to my controller. What I do not understand is, how do I get dynamically generated data in my view to the javascript to be sent?
Right now my view receives an instance variable on every HTTP request, it loops over every instance variable and makes a button which submits a hash with information extracted from that instance variable. I do not understand how to do the same thing with Javascript because Javascript will not understand Ruby classes.
This is what my code looks like now:
View/dashboards/_other_characters.html.erb
<% other_characters.each do |other_character| %>
<p><%= other_character.name %> is standing here (<%= other_character.power_level %>)</p>
<%= button_to "punch #{other_character.name}",
attacks_path(
target_type: other_character.class,
attack_type: :punch,
target_id: other_character,
target_name: other_character.name
) %>
<% end %>
This is what I would like to be able to do using JS
var task = {
name: 'Start taking advantage of WebSockets',
completed: false
}
var dispatcher = new WebSocketRails('localhost:3000/websocket');
dispatcher.trigger('tasks.create', task);
Try
<%= button_to "punch #{other_character.name}",
attacks_path(
target_type: other_character.class,
attack_type: :punch,
target_id: other_character,
target_name: other_character.name
), {id: '***', data: {name: '***', other_key: 'other_value'} } %>
Then you can access the data via jQuery data api.
When you need get dynamic data from view by client js, add data-attrs in your view dom then read it from dom API or other 3rd party js API.
In order to pass information from ruby to javascript you can use this gem: Gon, basically it transforms ruby variables and make them available to javascript on each view, take a look:
http://railscasts.com/episodes/324-passing-data-to-javascript
for any other alternative to achieve your purpose visit: https://www.ruby-toolbox.com/categories/javascript_tools#paloma
After completing Hartl's tutorial I'm trying to implement #replies for the Micropost model.
I wanted the reply button on a micropost to render the micropost form right there under said post a la twitter. I also wanted to pass the :micropost_id of said post to the reply so that I could later reference which post it was a reply to, again a la twitter.
I've been trying to implement some of that based on the answer to this question.
My microposts controller has
def reply_form
respond_to do |format|
format.js
end
end
The link in the view is
<%= link_to 'reply', 'shared/reply_form', remote: true, locals: { object: :id } %>
With my attempt there to pass on the micropost :id
After a post I have this for the partial to be rendered in:
<div id="ReplyContainer"></div>
I then have _reply_form.js.erb
$('#ReplyContainer').html('<%=j render partial: 'shared/reply_form', locals: { object: :id } %>')
It's not throwing errors but clicking the 'reply' link has no effect and does not render the _reply_form.html.erb partial.
My ultimate goal is you click reply, that renders the micropost form, with #username at the start of the message (derived from the micropost_id?) submitting that form then saves the new micropost including the micro post_id of the original post in the reply_to column that I've created in my microposts table. Any nudges in the right direction much appreciated. Don't want to be barking up the completely wrong tree. Thanks.
EDIT: I'm currently getting a routing error when clicking the reply link.
(No route matches [GET] "/shared/reply_form")
But as far as I can see everything is in the right place.
Ok, sorry, I've just read better your question and the problem is related to the wrong structure.
you have a controller with an action that will render the post
your post page will be composed by the "post" and many replies
each reply I guess is made of a partial (let's say a _reply.html.erb file).
The structure of the page will be then the following:
<div>
... my post here...
</div>
<div id='repliesContainer'>
<%- #replies..each do |id| -%>
<%= render :partial => "shared/reply", :locals => { :object => id} %>
<%- end -%>
</div>
And we call it post.html.erb.
This is will be your shared/_reply.html.erb file:
// note here that the id of the div contains the ID of the reply: each ID must be unique in the page....
<div id='ReplyContainer-#{object}'>
...reply content here...
</div>
Now, your post.js.erb file contains the following:
$('#repliesContainer').append("<%= escape_javascript( render :partial => 'shared/reply', :locals => { :object => id}) -%>");
The content of the append() function will be rendered on the server as a string from the partial.
You have several problems here:
As you discovered, the route to your reply form is incorrect. Until you get this fixed, you won't be able to debug the rest of the system. Run rake routes | grep reply_form to find the URL for it (it won't be under /shared most likely), then see what the response is for that URL. I bet it throws an error (see #2).
Your reply form file has the wrong name: _reply_form.js.erb is a partial but it needs to be 'reply_form.js.erb'. If you hit the correct route for that controller, you'll get a 'Missing Template' error because it is looking for a normal template, not a partial.
Finally, the reply_form.js.erb code needs to point to the shared reply_form partial, so if is really in shared/_reply_form.html.erb then the JS response should be rendered correctly.
Having said that...I really dislike sending Javascript back to the browser, in part because it makes debugging JS much harder - how are you going to find the returned code in your browser debugger? There may be some use cases for this type of design but they are probably very rare.
The better / more idomatic solution is to send JSON data (eg. the rendered text) back to the browser and have some handler parse the JSON and update the DOM. This way all of your JS code is present in the browser and all you are receiving is data. This will be much easier to debug.
In rails, this is an api response I'm currently generating as a response from /charts_json/south_carolina.json (for example)
[{"d_s":0,"name":"summerville"},{"d_s":1,"name":"hilton head island"},{"d_s":2,"name":"north myrtle beach"},{"d_s":1,"name":"spartanburg"},{"d_s":12,"name":"greenville, sc"},{"d_s":0,"name":"aiken"},{"d_s":6,"name":"columbia"},{"d_s":4,"name":"myrtle beach"},{"d_s":1,"name":"simpsonville"},{"d_s":1,"name":"lancaster, sc"},{"d_s":0,"name":"north augusta"},{"d_s":0,"name":"sumter"},{"d_s":0,"name":"rock hill"},{"d_s":1,"name":"beaufort, sc"},{"d_s":1,"name":"mount pleasant"},{"d_s":21,"name":"charleston"},{"d_s":1,"name":"clemson"},{"d_s":1,"name":"anderson, sc"}]
Now what I need to do is render the above like this, as a json document
[['0', 'summerville'], ['1', 'hilton head island'], ...etc... ]
For the benefit of the SO community and the clarification of the reader I'll include all the code I'm going to be using to make this work if and when I get this last thing handled
In addition to my charts_controller, I generated a charts_json_controller for responding to json requests--- an example of a controller method in that controller (this is a bit clunky but its ok for now as long as I get functionality)
def south_carolina
#locations = Location.find(1687).descendants #used acts_as_tree gem
respond_to do |format|
format.json { render json: #location.as_json(only: [:d_s, :name])}
end
In the view (cross section)
function drawMarkersMap() {
var data = google.visualization.arrayToDataTable([
['Startups', 'Location'],
$.ajax({url: '/charts_json/south_carolina', dataType: 'json'})
]);
Not sure if I'm understanding correctly, but here's a way to get the json as an array instead of a hash.
define this method for Location
class Location
def self.as_json_array
as_json(only: [:d_s, :name]).collect { |l| [l[:d_s], l[:name]] }
end
end
You could make this more general-purpose if you necessary, but I want to make sure I'm understanding your requirements first.
Then just use that method instead of as_json in your render line.
Also, it sounds like you know this, but you really should just use the same controller and put any custom code for different formats in your respond_to block.
I am trying to access a field in a Ruby on Rails object from my view inside some JavaScript. Here is the relevant code in my view pick.html.erb:
var directionDisplay;
var start = <%= #route.from %>;
var end = <%= #route.to %>;
From and to are text fields that hold zipcodes. Here is my relevant Route controller code:
class RoutesController < ApplicationController
# GET /routes/1/pick
def pick
#routes = Route.find(params[:id])
end
I read somewhere that I need to use ActiveSupport::JSON in order to access an object field...Do I need to do this instead? If so, how do I install it and could you possibly give me an example on how to get started? I've been searching for examples everywhere the past few days and I can't find any.
Let me know if you need any more code and thank you in advance for any reply!
The file name should be (according to convention) pick.js.erb and not pick.html.erb based on the fact that you have JavaScript code included.
If you want to include a partial inside this one, you can use the escape_javascript helper to render the partial.
If you have more than one possible file to be rendered from the pick action, you should look into using respond_to such as:
#route = Route.find(params[:id])
respond_to do |format|
format.html
format.js { render :js => #route }
end