I am getting my products index from another custom developed class which is passed to the index.html.erb
for rendering in a table format, using DataTables
and JavaScript
in the index.html.erb
page. This code is woking very well so far, doing pagination
, sorting
and filtering
on the server side
, but now I want to get a @products
variable from the JSON
used by DataTables
which I want to process it in the ProductsController
before it is passed as a @hash
to another JavaScript
eventualy to render the @markers
of the @products
on a Google Map
.
Here is my code:
products_controller.rb
...
def index
respond_to do |format|
format.html
format.json { render json: ProductsDatatable.new(view_context) }
end
end
...
index.html.erb
<div class="container-fluid">
<h1>Products</h1>
<table id="products" width="100%" class="display cell-border compact hover order-column row-border stripe" data-source="<%= products_url(format: "json") %>">
<thead>
<tr>
<th style="text-align: center">Product ID</th>
<th style="text-align: center">Product Name</th>
<th style="text-align: center">Category</th>
<th style="text-align: center">Release Date</th>
<th style="text-align: center">Price</th>
<th style="text-align: center">Created At</th>
<th style="text-align: center">Updated At</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<script>
$(document).ready(function() {
$('#products').dataTable({
sPaginationType: "full_numbers",
bJQueryUI: true,
bProcessing: true,
bServerSide: true,
sAjaxSource: $('#products').data('source'),
sDom: 'CRlfrtip',
bStateSave: true,
responsive: true
})
} );
</script>
Custom class fetching the records from server with pagination, sorting and filtering.
`/datatables/products_datatables.rb`
class ProductsDatatable
delegate :params, :link_to, :number_to_currency, to: :@view
def initialize(view)
@view = view
end
def as_json(options = {})
{
sEcho: params[:sEcho].to_i,
iTotalRecords: Product.count,
iTotalDisplayRecords: products.total_entries,
aaData: data
}
end
private
def data
products.map do |product|
[
link_to(product.id, product),
product.product_name,
product.category,
product.release_date.strftime("%Y-%m-%d"),
number_to_currency(product.price),
product.created_at.strftime("%Y-%m-%d %I:%M%p"),
product.updated_at.strftime("%Y-%m-%d %I:%M%p")
]
end
end
def products
@products ||= fetch_products
end
def fetch_products #this version genertes more optimized queries for the db
if params[:sSearch].present?
products = Product
.where("product_name like :search
or category like :search
or date_format(release_date, '%Y-%m-%d') like :search
", search: "%#{params[:sSearch]}%"
)
.order("#{sort_by}")
.page(page).per_page(per_page)
else
products = Product
.order("#{sort_by}")
.page(page).per_page(per_page)
end
products
end
def page
params[:iDisplayStart].to_i/per_page + 1
end
def per_page
params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 10
end
def sort_by
columns = %W[id product_name category release_date price created_at updated_at]
s = String.new
if params[:iSortCol_0].present?
s = s + "," + columns[params[:iSortCol_0].to_i] + " " + sort_direction(params[:sSortDir_0])
end
if params[:iSortCol_1].present?
s = s + "," + columns[params[:iSortCol_1].to_i] + " " + sort_direction(params[:sSortDir_1])
end
if params[:iSortCol_2].present?
s = s + "," + columns[params[:iSortCol_2].to_i] + " " + sort_direction(params[:sSortDir_2])
end
if params[:iSortCol_3].present?
s = s + "," + columns[params[:iSortCol_3].to_i] + " " + sort_direction(params[:sSortDir_3])
end
if params[:iSortCol_4].present?
s = s + "," + columns[params[:iSortCol_4].to_i] + " " + sort_direction(params[:sSortDir_4])
end
if s.empty?
s = columns[params[:iSortCol_0].to_i] + " " + sort_direction(params[:sSortDir_0])
end
if s[0] == ","
s.slice!(0)
end
s
end
def sort_direction (n)
n == "desc" ? "desc" : "asc"
end
end
How i can get the @products
in the controller, such a way when user navigates between the pages index pages, to have the content of @products
changed accordingly?
Here is the same old problem persisting even after I received advise and I reworked the code. When I am using the navigation buttons for the data table to move to another page, first, last, previous, next etc, the table content changes accordingly, but the map shows always the same first 10 products
.
The Google Map is never changing when the content rendered in the table changes. How I can fix this?Don't know how to change my code to make it working properly?
What I need is to have the @hash
content changing when the @products
is changed with the pagination. How this can be done?
products_controller.rb
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
# GET /products
# GET /products.json
def index
if params[:sSearch].present?
@products = Product
.where("product_name like :search
or category like :search
or date_format(release_date, '%Y-%m-%d') like :search
", search: "%#{params[:sSearch]}%"
)
.order("#{sort_by}")
.page(page).per_page(per_page)
else
@products = Product
.order("#{sort_by}")
.page(page).per_page(per_page)
end
@hash = Gmaps4rails.build_markers(@products) do |product, marker|
marker.lat product.lat || 0.0
marker.lng product.lng || 0.0
title = product.product_name
marker.infowindow make_content_for_infowindow(product)
marker.json({:title => title})
end
@products_data = @products.map do |product|
[
product.id,
product.created_at.strftime("%Y-%m-%d %H:%M:%S %Z"),
product.updated_at.strftime("%Y-%m-%d %H:%M:%S %Z"),
product.product_name,
product.category,
product.release_date ? product.release_date.strftime("%Y-%m-%d") : nil,
product.price,
product.current_location,
product.lat,
product.lng
]
end
@json = {
"sEcho" => params[:sEcho].to_i,
"iTotalRecords" => @products.count,
"iTotalDisplayRecords"=> @products.total_entries,
"aaData" => @products_data.as_json
}
respond_to do |format|
format.html
format.json { render :json=> @json }
end
end
# GET /products/1
# GET /products/1.json
def show
end
# GET /products/new
def new
@product = Product.new
end
# GET /products/1/edit
def edit
end
# POST /products
# POST /products.json
def create
@product = Product.new(product_params)
respond_to do |format|
if @product.save
format.html { redirect_to @product, notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: @product }
else
format.html { render :new }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
respond_to do |format|
if @product.update(product_params)
format.html { redirect_to @product, notice: 'Product was successfully updated.' }
format.json { render :show, status: :ok, location: @product }
else
format.html { render :edit }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end
# DELETE /products/1
# DELETE /products/1.json
def destroy
@product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
@product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.fetch(:product, {})
end
def page
params[:iDisplayStart].to_i/per_page + 1
end
def per_page
params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 10
end
def sort_by
#these should be enumerated in the same order as they appear rendered in the table from left to right
columns = %W[id created_at updated_at product_name category release_date price current_location lat lng]
s = String.new
if params[:iSortCol_0].present?
s = s + "," + columns[params[:iSortCol_0].to_i] + " " + sort_direction(params[:sSortDir_0])
end
if params[:iSortCol_1].present?
s = s + "," + columns[params[:iSortCol_1].to_i] + " " + sort_direction(params[:sSortDir_1])
end
if params[:iSortCol_2].present?
s = s + "," + columns[params[:iSortCol_2].to_i] + " " + sort_direction(params[:sSortDir_2])
end
if params[:iSortCol_3].present?
s = s + "," + columns[params[:iSortCol_3].to_i] + " " + sort_direction(params[:sSortDir_3])
end
if params[:iSortCol_4].present?
s = s + "," + columns[params[:iSortCol_4].to_i] + " " + sort_direction(params[:sSortDir_4])
end
if s.empty?
s = columns[params[:iSortCol_0].to_i] + " " + sort_direction(params[:sSortDir_0])
end
if s[0] == ","
s.slice!(0)
end
s
end
def sort_direction (n)
n == "desc" ? "desc" : "asc"
end
def make_content_for_infowindow(product)
s = String.new
s << "Name: "
s << product.product_name
s << " ID: "
s << product.id.to_s
s << " Category: "
s << product.category
s << " Price: "
s << product.price.to_s
s
end
end
index.html.erb
div class="container-fluid">
<h1>Products</h1>
<table id="products" width="100%" class="display cell-border compact hover order-column row-border stripe" data-source="<%= products_url(format: "json") %>">
<thead>
<tr>
<th style="text-align: center">Product ID</th>
<th style="text-align: center">Created At</th>
<th style="text-align: center">Updated At</th>
<th style="text-align: center">Product Name</th>
<th style="text-align: center">Category</th>
<th style="text-align: center">Release Date</th>
<th style="text-align: center">Price</th>
<th style="text-align: center">Current Location</th>
<th style="text-align: center">Latitude</th>
<th style="text-align: center">Longitude</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="row-fluid">
<div id="map" style='width: 100%; height: 500px; border: 1px solid black;'>
</div>
</div>
<br>
<script>
$(document).ready(function() {
$('#products').dataTable({
sPaginationType: "full_numbers",
bJQueryUI: true,
bProcessing: true,
bServerSide: true,
sAjaxSource: $('#products').data('source'),
sDom: 'CRlfrtip',
bStateSave: true,
responsive: true
})
} );
</script>
<script type="text/javascript">
buildMap (<%=raw @hash.to_json %>);
</script>
Coffee script for the Google Map to be rendered gmaps_google.js.coffee
class RichMarkerBuilder extends Gmaps.Google.Builders.Marker #inherit from builtin builder
#override create_marker method
create_marker: ->
options = _.extend @marker_options(), @rich_marker_options()
@serviceObject = new RichMarker options #assign marker to @serviceObject
rich_marker_options: ->
marker = document.createElement("div")
marker.setAttribute 'class', 'marker_container'
marker.innerHTML = @args.title
_.extend(@marker_options(), { content: marker })
infobox: (boxText)->
content: boxText
pixelOffset: new google.maps.Size(-140, 0)
boxStyle:
width: "400px"
# override method
create_infowindow: ->
return null unless _.isString @args.infowindow
boxText = document.createElement("div")
boxText.setAttribute("class", 'marker_info_box') #to customize
boxText.innerHTML = @args.infowindow
@infowindow = new InfoBox(@infobox(boxText))
@buildMap = (markers)->
handler = Gmaps.build 'Google', { builders: { Marker: RichMarkerBuilder} } #dependency injection
#then standard use
handler.buildMap { provider: {}, internal: {id: 'map'} }, ->
markers = handler.addMarkers(markers)
handler.bounds.extendWith(markers)
handler.fitMapToBounds()