Context: I have a Rails backend serving as an API to an Angular.JS front-end application.
Task: I want to retrieve all of the records of different species of "dinosaurs" from the Rails backend. Since there are over 500 records, I want to only get 30 species at a time.
My current approach: I am using the will_paginate gem in my Rails index controller action for the dinosaurs_controller. I have it running like this.
def index
#dinosaurs = Dinosaur.paginate(:page => params[:page], :per_page => 30)
end
In my Angular code:
I have a module called DinoApp and am using ngresource to create an Entry resource
app = angular.module("DinoApp", ["ngResource"])
app.factory "Entry", ["$resource", ($resource) ->
$resource("/api/v1/dinosaurs/:id", {id: "#id"}, {update: {method: "PUT"}} )
]
My Angular controller looks like this:
#MainController = ["$scope", "Entry", ($scope, Entry) ->
$scope.entries = Entry.query({page: 1})
$scope.viewPost = (dinosaurId) ->
]
This line of code would hit the API at dinosaurs_controller's index action and will only return 30 species of "dinosaurs" at a time:
$scope.entries = Entry.query({page: 1})
Now - how would I get angular.js to show a next page button and append the next page to the view?
I created a directive for this, which might be helpful:
https://github.com/heavysixer/angular-will-paginate
You can use a counter for the number of pages that gets incremented each time your controller gets called.
var counter = 1;
$scope.loadPage = function() {
$scope.entries = Entry.query({page: counter})
counter += 1 ;
}
And have a button that refer to that next page.
<button ng-click="loadPage()">Next page</button>
Related
Consider the drag-and-drop sorting table implemented on http://benw.me. Table rows are moved - but when I restart the page, the location of the rows is not persisted, and reverts to the original.
Error query AJAX:
screenshot #1
update_row_order error 400
Controller:
def update_row_order
#task = Task.find(task_params[:task_id])
#task.row_order_position = task_params[:row_order_position]
#task.save
render nothing: true # this is a POST action, updates sent via AJAX, no view rendered
end
private
# Use callbacks to share common setup or constraints between actions.
def set_thing
#task = Task.find(params[:id])
end
Tasks.coffee:
jQuery ->
$('.best_in_place').best_in_place();
jQuery ->
if $('#sortable').length > 0
table_width = $('#sortable').width()
cells = $('.table').find('tr')[0].cells.length
desired_width = table_width / cells + 'px'
$('.table td').css('width', desired_width)
$('#sortable').sortable(
axis: 'y'
items: '.item'
cursor: 'move'
sort: (e, ui) ->
ui.item.addClass('active-item-shadow')
stop: (e, ui) ->
ui.item.removeClass('active-item-shadow')
# highlight the row on drop to indicate an update
ui.item.children('td').effect('highlight', {}, 1000)
update: (e, ui) ->
item_id = ui.item.data('item-id')
console.log(item_id)
position = ui.item.index() # this will not work with paginated items, as the index is zero on every page
$.ajax(
type: 'POST'
url: '/tasks/update_row_order'
dataType: 'json'
data: { thing: {task_id: item_id, row_order_position: position } }
)
)
Had the same problem, make sure to include :task_id in the tasks_params. So it should look like:
def task_params
params.require(:task).permit(:task_id, :other_params, :row_order_position, :other_param)
end
In a rails project, I have 1 controller and 2 model. I want send 1 request from angularjs to rails server and for response, get 2 json array, 1. first model. 2. seccond model.
Now I use below code, and get just 1 of 2 array:
Rails Contorller: tables_controller.rb:
class Api::V1::TablesController < Api::V1::BaseController
def index
#table = Table.all
#mostagheltype = Mostagheltype.all
//I can just send 1 of model.
respond_with(Table.all)
end
end
Angularjs Controller: table.js:
$scope.tables = Tables.index();
tableService.js:
'use strict';
var app = angular.module('tableService', ['ngResource']);
app.factory('Tables', function($resource) {
return $resource('/api/tables.json', {}, {
index: { method: 'GET', isArray: true}
});
});
I can push 2 table in 1 array in rails controller and then recieve this from angular controller, like below:
array = []
Table.all.each do |table|
array << { name: table.name, check: 1 }
end
Mostagheltype.all.each do |m|
array << { name: mostagheltype.name, check: 2}
end
//I can seprate 2 array by `check` value in angularjs part.
but I want a solution that I send each array separate. How can I do this? Any idea?
Short answer no. You can't have two responses to one request. You can respond with an object that contains both arrays.
My is a little rusty. So this may be more psuedo code than ruby;
obj = {}
obj.array1 =[]
obj.array2 =[]
Then populate each array and return the object.
I have this in my html template. I want the button to fire the showMore function when clicked.
<button ng-click="showMore(secondPage)">Show More</button>
This is the controller. I'm not sure why $scope.nextPage will increment up everywhere except for at $scope.secondPage. I want $scope.secondPage to do fire on the first click Entry.query({page: 2}) then fire Entry.query({page: 3}) on the next click. But right now it keeps firing page 2.
#MainController = ["$scope", "Entry", ($scope, Entry) ->
$scope.entries = Entry.query({page: 1})
$scope.nextPage = 2
$scope.secondPage = Entry.query({page: $scope.nextPage})
$scope.showMore = (secondPage) ->
i = 0
while i < secondPage.length
$scope.entries.push(secondPage[i])
i++
$scope.nextPage++
console.log("nextpage" + $scope.nextPage)
]
I am using coffeescript. I don't fully understand why the page number in $scope.secondPage is not incrementing.
I'd do:
<button ng-click="showMore()">Show More</button>
#MainController = ["$scope", "Entry", ($scope, Entry) ->
$scope.entries = []
current_page = 1
$scope.showMore = ->
on_success = (entries)->
$scope.entries = $scope.entries.concat entries
current_page = current_page + 1
Entry.query {page: current_page}, on_success
$scope.showMore()
]
I'm working with the gmaps4rails gem (version 1.5.6), and I want to add the ability to add more Polylines to a map rather than replacing the Polylines that are already there. On github, this code is viewable here.
This feature already exists for Markers:
Gmaps.map.replaceMarkers(your_markers_json_array); Gmaps.map.addMarkers(your_markers_json_array);.
The Markers feature seems to be laid out in two places:
1) In gmaps4rails.base.js as:
Gmaps4Rails.prototype.addMarkers = function(new_markers) {
this.markers = this.markers.concat(new_markers);
this.create_markers();
return this.adjustMapToBounds();
};
2) In gmaps4rails.base.js.coffee as:
#add new markers to on an existing map
addMarkers : (new_markers, adjustBounds = true) ->
#update the list of markers to take into account
#markers = #markers.concat(new_markers)
#put markers on the map
#create_markers()
#adjustMapToBounds() if adjustBounds
I figured I could work with the replacePolylines code in to make my own addPolylines call:
1) In gmaps4rails.base.js near the replacePolylines code as:
Gmaps4Rails.prototype.addPolylines = function(new_polylines) {
this.polylines = this.polylines.concat(new_polylines);
this.create_polylines();
return this.adjustMapToBounds();
};
2) In gmaps4rails.base.js.coffee near the replacePolylines code as:
#add new polylines to on an existing map
addPolylines : (new_polylines) ->
#update the list of polylines to take into account
#polylines = #polylines.concat(new_polylines)
#put polylines on the map
#create_polylines()
#.... and adjust map boundaries
#adjustMapToBounds()
I've made these changes to the gem that's already added to my Rails project, and I've restarted my Rails server. I call it the same way I call replacePolylines, with Gmaps.map.addPolylines(your_polylines_json_array);. It produces an error in the console: Uncaught TypeError: Object #<Gmaps4RailsGoogle> has no method 'addPolylines'.
There doesn't seem to be anywhere else in the gmaps4rails project where I have to do anything for my addPolylines call, but I'm obviously not doing something right. Can anyone explain what I need to do to get this to work, based on this information?
As a workaround, until I can figure out how to build the functionality into gmaps4rails itself, I've taken these measures to asynchronously load Polylines onto a map...
In my UsersController, I've set up the show action to build a blank #polylines_json set:
def show
#user = User.find(params[:id])
#polylines_json = {}
end
The idea here is to have nothing for gmaps4rails to display on its initial map load, so that the initial page display is as quick as possible. In the view, I'm loading the map with this line:
<%= gmaps( :polylines => { :data => #polylines_json }, :map_options => { :type => 'HYBRID', :zoom => 12, :auto_adjust => false, :center_latitude => "-77", :center_longitude => "21" } ) %>
Back in the UsersController, I've got a custom action maps that is set up to handle json requests that contain from and to parameters (so that I can find items within specific ranges). It's in this action that I'm building the actual Polyline json data based on the from/to parameters:
def items
items = items.find(:all, :conditions => ['id >= ? AND id <= ?', params[:from], params[:to]])
polyline = []
i=0
items.each do |item|
polyline[i] = []
polyline[i] += [{:lng=>item.longitude.to_f,:lat=>item.latitude.to_f}]
i += 1
end
#polylines_json = polyline.to_json
respond_to do |format|
format.json { render json: #polylines_json }
end
end
Finally, back in the view to bring it all together, I'm asynchronously building up the Polyline collection ten at a time until I retrieve them all from the database:
<% content_for :scripts do %>
<script type="text/javascript" charset="utf-8">
numberOfItems = <%= #user.items.count %>;
var polylines= [];
function LoadMap(from, to) {
if (to < numberOfItems){
nextFrom = to + 1;
nextTo = to + 10;
}
if(nextTo <= numberOfItems){
$.ajax({
dataType: 'json',
url: "http://<%= request.host %>/users/<%= #user.id %>/maps.json?from="+from+"&to="+to,
success: function(response) {
polylines = polylines.concat(response);
Gmaps.map.replacePolylines(polylines);
LoadMap(nextFrom, nextTo);
}
});
}
}
LoadMap(1,10);
</script>
<% end %>
I'm trying to make code from a Sinatra app work in the Rails context. The Sinatra app uses ajax requests to trigger the Sinatra routes/controller actions. For example, if you trigger the new function on a javascript model
new: function() {
var _this = this;
$.ajax({
url: "/gamestart",
type: "POST",
....
It will trigger the route/controller code in the Sinatra app
post "/new" do
end
When I tried to make this work in Rails, I'm getting a 500 internal server error. In my Rails app, the new_game button triggers an ajax request to a Rails route which triggers a controller action, and that controller action uses the Rails model to get data from the database. For some reason that doesn't seem like the right way to do it in Rails, and I'm wondering if it's the reason I'm getting the server error
GET http://localhost:3000/gamestart 500 (Internal Server Error)
If possible, can you tell me where in the chain of actions outlined below that error is arising and what I might do to fix it.
1 Click on the new game button triggers 'startNewGame' method
'click #new_game': 'startNewGame',
2 The startNewGame method calls method on Game model
startNewGame: function() {
this.model.new();
},
3 The new method in the Game model makes a GET request to the url '/gamestart'. I also tried a post request. I don't know why it would need to be a post request, but neither worked. (In the original Sinatra application, the gamestart url led immediately into the function post '/gamestart' do...)
new: function() {
var _this = this;
$.ajax({
url: "/gamestart",
type: "GET", \\\ also tried POST
success: function(response) {
var json = $.parseJSON(response);
_this.set({lost: false});
_this.set({win: false});
_this.trigger("gameStartedEvent", json);
}
})
},
4 I directed the url to a controller action in Rails router file
match 'gamestart' => 'locations#gamestart', :via => :get
Note, in the original Sinatra application, the route and the controller action were combined
5 The gamestart method of the locations_controller.rb
def gamestart
word = Word.get_random
masquerade_word = Word.masquerade(word)
session[:word] = word
session[:incorrect_guesses] = 0
session[:chars_left] = word.size
session[:revealed_word] = masquerade_word
{:word => masquerade_word}.to_json
end
6 The get_random method on the word model Word.rb, which is called from locations controller
def get_random
words = []
locations = Location.all (this pulls up the names of the locations from the db)
locations.each do |e|
words << e.name
end
words.sample
end
ERROR MESSAGE
GET http://localhost:3000/gamestart 500 (Internal Server Error) jquery.js:8215
XHR finished loading: "http://localhost:3000/gamestart". jquery.js:8215
send jquery.js:8215
jQuery.extend.ajax jquery.js:7767
window.Game.Backbone.Model.extend game.js:27
window.OptionsView.Backbone.View.extend.startNewGame optionsView.js:14
jQuery.event.dispatch jquery.js:3062
elemData.handle.eventHandle
Note, in the original Sinatra application, the route and the controller action were combined in the usual Sinatra way
post "/gamestart" do
word = Word.get_random
masquerade_word = Word.masquerade(word)
session[:word] = word
session[:incorrect_guesses] = 0
session[:chars_left] = word.size
session[:revealed_word] = masquerade_word
{:word => masquerade_word}.to_json
end
UPDATE
The 500 error seemed to be triggered by a missing template. This method in locations controller wasn't rendering anything. It didn't have a view file. I therefore changed the controller to make it respond_to :json and then use respond_with at the end of the action, but that triggered a 406 error.
def gamestart
word = Word.get_random
masquerade_word = Word.masquerade(word)
session[:word] = word
session[:incorrect_guesses] = 0
session[:chars_left] = word.size
session[:revealed_word] = masquerade_word
{:word => masquerade_word}.to_json
end
became now triggers 406 error
respond_to :json
def gamestart
word = Word.get_random
masquerade_word = Word.masquerade(word)
session[:word] = word
session[:incorrect_guesses] = 0
session[:chars_left] = word.size
session[:revealed_word] = masquerade_word
plainvariable = {:word => masquerade_word}.to_json ###changed
respond_with plainvariable ###changed
end
You say that your gamestart controller method is causing a server error due to a missing template. If we look at that controller method:
def gamestart
#...
{:word => masquerade_word}.to_json
end
we see that it returns a JSON string but it neglects to render anything. You don't call any rendering or redirection methods so Rails helpfully (ha ha) assumes that you want to render the gamestart view template; but, you have no such thing so you get an error.
You should render your JSON, not return it; something more like this:
def gamestart
#...
render :json => { :word => masquerade_word }
end