Open matissg opened 7 years ago
I am not sure that you need to do in this gem. you can do this separetely. also you can save state in client side(browser) - https://datatables.net/reference/option/stateLoadCallback
$(document).ready(function() {
$('#datatable').DataTable({
stateSave: true
});
} );
@ajahongir Sure, I don't do this in the gem + I'm aware of this JS.
The idea is to have table, e.g., datatables_states
where user_id, state name, state status would be stored. Then with callback either state is saved or loaded from DB. I've started to work on this kind of implementation already as I see it as nice feature for app with lots of tables - user can save their table states to have more customized experience. Would you be interested to take a look on my code?
I have Model and DB done, Controller is almost done (method logic is done), however I have stuck a bit of how to get name of each table captured and stored in DB.
so here is an example of usage.
$('#example').DataTable( {
stateSave: true,
stateLoadCallback: function (settings, callback) {
$.ajax( {
url: '/state_load',
async: false,
dataType: 'json',
success: function (json) {
callback( json );
}
} );
}
} );
you can pass datatable if with request url so example will look like
$('#example').DataTable( {
stateSave: true,
stateLoadCallback: function (settings, callback) {
$.ajax( {
url: '/datatables_states/[datatable_id]',
async: false,
dataType: 'json',
success: function (json) {
callback( json );
}
} );
}
} );
so in controller
def show
state = DatatableState.find params[:id]
render json: state
end
@ajahongir Here what I'm trying to implement.
The idea is as follows:
1/ in Index View there is a table identified by this line
<table ... data-controller-name="<%= controller.get_controller_name %>" ... >
2/ in application_controller.rb there is this method
def get_controller_name
@controller_name = self.class.name.split("::").last
end
which basically gets Controller name, e.g. "UsersController" 3/ then there is table where states are saved
class CreateCommonDatatableStates < ActiveRecord::Migration[5.1]
def change
create_table :common_datatable_states do |t|
t.belongs_to :user, index: true
t.string :name, index: true
t.string :state
t.timestamps
end
end
end
So I'd save current_user ID, ControllerName and particular table state.
4/ appropriate Model looks like this:
class Common::DatatableState < ApplicationRecord
belongs_to :user, class_name: 'User'
validates :user, presence: true
validates :name, length: { in: 1..70, too_long: "%{count} characters is the maximum allowed" }
validates :state, length: { in: 1..10000, too_long: "%{count} characters is the maximum allowed" }
end
5/ table states are saved and loaded with this Controller:
class Common::DatatablesStatesController < ApplicationController
before_action :table_state
#This is for updating existing and saving new table states
def state_save
puts @table_state
if @tables_by_name.include?(@controller_name)
#update action
@table_state.update(datatable_states_params)
render json: { params: { common_datatable_state: {
user_id: current_user.id, name: @controller_name, state: state
} } }
elsif
#create action
current_user.datatable_states.create!(datatable_states_params)
render json: { params: { common_datatable_state: {
user_id: current_user.id, name: @controller_name, state: state
} } }
else
#error message
render json: 'error'
end
end
#This is for loading existing table states for current_user
def state_load
puts @table_state
if @tables_by_name.include?(@controller_name)
state = @table_state.pluck(:state)
render json: { data: { state: state } }
else
render json: 'error'
end
end
private
def datatable_states_params
params.require(:common_datatable_state).permit(:user_id, :name, :state)
end
def table_state
@controller_name = #Here I'm missing implementation of how to read ControllerName
from the view
@tables_by_name = current_user.datatables_states.pluck(:name)
@table_state = current_user.datatables_states.where(name: @controller_name)
end
end
In my controller method table_state
I'm missing code for getting controller name from table view. This controller would 1) save / update table state for current_user, 2) load state for particular table searched by ControllerName for current_user
6/ My routes are:
get '/datatables/state_save', to: 'common/datatables_states#state_save'
get '/datatables/state_load', to: 'common/datatables_states#state_load'
7/ Last is Coffeescript, which looks like this:
$ ->
$('table[data-controller-name]').DataTable
ajax: $('table[data-controller-name]').data('source')
processing: true
serverSide: true
responsive: true
stateSave: true
colReorder: true
language:
search: '_INPUT_'
searchPlaceholder: 'Search by name ...'
pagingType: 'full_numbers'
dom: '<"html5buttons"B><"top"lf>rt<"bottom"p><"clear">'
'aoColumnDefs': [ {
'bSortable': false
'aTargets': [ -1 ]
} ]
buttons: [
'copyHtml5'
'excelHtml5'
'csvHtml5'
'pdfHtml5'
{
extend: 'print'
customize: (win) ->
$(win.document.body).addClass 'white-bg'
$(win.document.body).css 'font-size', '10px'
$(win.document.body).find('table').addClass('compact').
css 'font-size', 'inherit'
return
}
'colvis'
]
#This is for stateSave serverside
'stateSaveCallback': (settings, data) ->
# Send an Ajax request to the server with the state object
$.ajax
'url': '/datatables/state_save'
'data': data
'dataType': 'json'
'type': 'POST'
'success': ->
return
#This is for stateLoad serverside
stateLoadCallback: (settings, callback) ->
$.ajax
url: '/datatables/state_load'
async: false
dataType: 'json'
success: (json) ->
callback json
return
return
return
Basically it works for any table with data-controller-name
. So this script shoud do state saving and loading, however it does not work correctly at the moment. I can open /datatables/state_load.json and there is some result so I assume I've set my implementation almost correct.
Would you, please, take a look on this and point to what should be changed to make it working? I'd be happy to have as flexible as possible solution as I have a lot of tables in my project and would like to have it as DRY as possible.
you dont need difine a method get_controller_name
instead:
<table ... data-controller-name="<%= params[:controller] %>" ... >
its shold be more easy. use post and get methods
class Common::DatatablesStatesController < ApplicationController
def update
datatables_state.update(datatable_states_params)
render json: datatables_state
end
def show
render json: datatables_state
end
private
def datatable_states_params
params.require(:common_datatable_state).permit(:state)
end
def datatables_state
@datatables_state ||= current_user.datatables_states.where(name: params[:id]).first_or_create
end
end
6.
resources :datatable_states, only: [:update, :show]
so now, you have to send request to /datatable_states/[data-controller-name].json
put and get requests
@ajahongir Thank you, looks nice. I should have thought of using update, show methods :)
I have few questions:
1) will it pass UsersController as param rather than just grab Common::DatatablesStatesController as name?
2) in def datatables_state
shouldn't it be params[:controller]
as we need to find state by controller name?
3) in this implementation I probably can remove before_action :table_state
right?
def datatables_state
your params[:controller] => datatable_states_controller
so its no what we need. we need datatable controller name right? so params[:id]
contains it.@ajahongir
If I have Datatable in users_path
with UsersController I would need to store UsersController as name in common_datatable_states
table name
column. I was thinking that would be method of how to save unique table names in DB. In things_path
I could have ThingsController and save that in DB as well, and so on.
Common::DatatablesStatesController
I would use for storing and loading table state info from DB - particular state info par current_user according to particular table name (e.g., "UsersController"). This means I don't need Common::DatatablesStatesController
name anywhere.
@ajahongir This is what I've done:
1/ since I have namespaced controllers, I adjusted my view a bit to this:
<table ... data-controller-name="<%= params[:controller].split("/").last %>" ... >
so now in the view I have this:
<table ... data-controller-name="users" ...>
otherwise it would include namespace and slash (e.g., user/users), which then later we cannot use for routing to json.
2/ my routes:
namespace :common do
resources :datatable_states, only: [:update, :show]
end
and now I see where [:id]
appears :)
common_datatable_state GET (/:locale)/common/datatable_states/:id(.:format)
You suggested such a clever solution!
In my Coffeescript I've put this:
#This is for StateSave serverside
'stateSaveCallback': (settings, data) ->
# Send an Ajax request to the server with the state object
$.ajax
'url': '/common/datatable_states/[data-controller-name].json'
'data': data
'dataType': 'json'
'type': 'POST'
'success': ->
return
#This is for stateLoad serverside
stateLoadCallback: (settings, callback) ->
$.ajax
url: '/common/datatable_states/[data-controller-name].json'
async: false
dataType: 'json'
success: (json) ->
callback json
return
return
return
At the moment when I navigate to /common/datatable_states/[data-controller-name].json
I see this Routing error uninitialized constant Common::DatatableStatesController
In concole I don't see any stateSave / stateLoad happening as well.
What am I missing, please?
@ajahongir Nevermind, I got typo as my routes should be resources :datatables_states, only: [:update, :show]
@ajahongir It seems that something is not working right as stateSave / Load callbacks are not working when current_user is changing / opening table. However when I navigate to http://localhost:3000/en/common/datatables_states/campaigns_index.json
record is created without saving state data (empty value):
Started GET "/en/common/datatables_states/campaigns_index.json" for 10.0.2.2 at 2017-05-10 17:13:35 +0000
Processing by Common::DatatablesStatesController#show as JSON
Parameters: {"locale"=>"en", "id"=>"campaigns_index"}
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
Common::DatatableState Load (2.2ms) SELECT "common_datatable_states".* FROM "common_datatable_states" WHERE "common_datatable_states"."user_id" = $1 AND "common_datatable_states"."name" = $2 ORDER BY "common_datatable_states"."id" ASC LIMIT $3 [["user_id", 2], ["name", "campaigns_index"], ["LIMIT", 1]]
(6.5ms) BEGIN
SQL (1.3ms) INSERT INTO "common_datatable_states" ("user_id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["user_id", 2], ["name", "campaigns_index"], ["created_at", "2017-05-10 17:13:35.357828"], ["updated_at", "2017-05-10 17:13:35.357828"]]
(1.8ms) COMMIT
Completed 200 OK in 145ms (Views: 1.2ms | ActiveRecord: 19.7ms)
When I navigate to particular link again, it loads the record:
Started GET "/en/common/datatables_states/campaigns_index.json" for 10.0.2.2 at 2017-05-10 17:16:18 +0000
Processing by Common::DatatablesStatesController#show as JSON
Parameters: {"locale"=>"en", "id"=>"campaigns_index"}
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
Common::DatatableState Load (3.1ms) SELECT "common_datatable_states".* FROM "common_datatable_states" WHERE "common_datatable_states"."user_id" = $1 AND "common_datatable_states"."name" = $2 ORDER BY "common_datatable_states"."id" ASC LIMIT $3 [["user_id", 2], ["name", "campaigns_index"], ["LIMIT", 1]]
Completed 200 OK in 18ms (Views: 1.3ms | ActiveRecord: 4.1ms)
Should I be changing something in my Coffeescript?
@ajahongir H! It's not an issue, rather I've been searching for solution to Datatables stateSaveCallback / stateLoadCallback server side. I've installed ajax-datatables-rails gem and would like to include that cool feature having state saved in DB.
I've checked this PHP example, however I was thinking if there is any example of how to do it for Rails? So far I didn't find anything in Google. I'd be happy for any help as I'm new in coding :) Thank you!