Skip to content

Commit

Permalink
4372 - Notes filterable table component
Browse files Browse the repository at this point in the history
  • Loading branch information
WillNigel23 committed Oct 25, 2024
1 parent 860b52c commit 16ead55
Show file tree
Hide file tree
Showing 40 changed files with 710 additions and 354 deletions.
3 changes: 3 additions & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,9 @@ $(function() {

$('[data-select-all]').selectAll();

// Filterable table
$('[data-filterable-table]').filterableTable();

// Category tree expand/collapse
$('.tree-bullet').on('click', function(e) {
e.preventDefault();
Expand Down
68 changes: 68 additions & 0 deletions app/assets/javascripts/plugins/filterable-table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Author: willnigeldejesus@gmail.com
* Date: 22th Oct 2024
*/

(function($) {

$.fn.filterableTable = function(options) {

const selector = this.attr('data-filterable-table-selector');

const $form = this;
const $table = this.find(`#${selector}`);

$form.on('keydown', 'input', function(event) {
if (event.key === 'Enter') {
event.preventDefault();
}
});

$form.on('change', function() {
$('html').addClass('page-busy');

const formData = $form.serialize();
const queryParams = new URLSearchParams(formData).toString();
const newUrl = `${window.location.pathname}?${queryParams}`;
history.pushState(null, '', newUrl);

$.ajax({
url: $form.attr('action'),
method: $form.attr('method'),
data: formData,
success: function(response) {
$table.html(response)
bindSortingClickEvent($table, $form);
$table.find('.dataTables_empty').attr('colspan', $table.find('th').length);
$table.find('.dataTables_empty').css('display', '');

$('html').removeClass('page-busy');
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('Error:', textStatus, errorThrown);

$('html').removeClass('page-busy');
}
});
});

bindSortingClickEvent($table, $form);
$table.find('.dataTables_empty').attr('colspan', $table.find('th').length);
$table.find('.dataTables_empty').css('display', '');

function bindSortingClickEvent($table, $form) {
$table.off('click', '.sorting');
$table.on('click', '.sorting', function(event) {
const $sortElement = $(this);
const sortParam = $sortElement.attr('data-sort');
const order = $sortElement.hasClass('sorting_asc') ? 'desc' : 'asc';

$form.find('#sort').val(sortParam);
$form.find('#order').val(order);

$form.trigger('change');
});
}
}

})(jQuery);
47 changes: 22 additions & 25 deletions app/controllers/export_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,15 @@ class ExportController < ApplicationController
include ExportHelper
include ExportService

DEFAULT_WORKS_PER_PAGE = 15

# no layout if xhr request
layout Proc.new { |controller| controller.request.xhr? ? false : nil }

def index
filtered_data

@table_export = @collection.works.joins(:table_cells).where.not(table_cells: { work_id: nil }).distinct
end

def list
filtered_data

render partial: 'list', locals: { collection: @collection, works: @works, header: @header }
render partial: 'table' if request.xhr?
end

def show
Expand Down Expand Up @@ -194,6 +190,10 @@ def update_contentdm_credentials
private

def filtered_data
@sorting = (params[:sort] || 'title').to_sym
@ordering = (params[:order] || 'ASC').downcase.to_sym
@ordering = [:asc, :desc].include?(@ordering) ? @ordering : :desc

# Check if there are any translated works in the collection
@header = @collection.works.where(supports_translation: true).exists? ? 'Translated' : 'Transcribed'

Expand All @@ -212,37 +212,34 @@ def filtered_data

sort_filtered_data

@works = @works.paginate(page: params[:page], per_page: params[:per_page] || 15) unless params[:per_page] == '-1'
if params[:per_page] != '-1'
@works = @works.paginate(page: params[:page], per_page: params[:per_page] || DEFAULT_WORKS_PER_PAGE)
end

@table_export = @collection.works.joins(:table_cells).where.not(table_cells: { work_id: nil }).distinct
end

def sort_filtered_data
return if params[:sort].blank?

direction = params[:order]&.downcase == 'desc' ? 'DESC' : 'ASC'

case params[:sort].downcase
when 'title'
sorting_arguments = "title #{direction}"
when 'page_count'
sorting_arguments = "work_statistics.total_pages #{direction}"
when 'indexed_count'
case @sorting
when :page_count
sorting_arguments = "work_statistics.total_pages #{@ordering}"
when :indexed_count
ordered_work_ids = calculate_ordered_work_ids(:progress_annotated)
ordered_work_ids.reverse! if direction == 'DESC'
ordered_work_ids.reverse! if @ordering == :desc

sorting_arguments = "FIELD(id, #{ordered_work_ids.join(',')})"
when 'completed_count'
when :completed_count
ordered_work_ids = calculate_ordered_work_ids(:progress_completed)
ordered_work_ids.reverse! if direction == 'DESC'
ordered_work_ids.reverse! if @ordering == :desc

sorting_arguments = "FIELD(id, #{ordered_work_ids.join(',')})"
when 'reviewed_count'
when :reviewed_count
ordered_work_ids = calculate_ordered_work_ids(:progress_review)
ordered_work_ids.reverse! if direction == 'DESC'
ordered_work_ids.reverse! if @ordering == :desc

sorting_arguments = "FIELD(id, #{ordered_work_ids.join(',')})"
else
sorting_arguments = ''
@works = @works.reorder(sorting_arguments)
sorting_arguments = "title #{@ordering}"
end

@works = @works.reorder(Arel.sql(sorting_arguments))
Expand Down
158 changes: 108 additions & 50 deletions app/controllers/notes_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
class NotesController < ApplicationController
include ActionView::Helpers::TextHelper
DEFAULT_NOTES_PER_PAGE = 15
PAGES_PER_SCREEN = 20

def index
filtered_notes

render partial: 'table' if request.xhr?
end

def create
unless user_signed_in?
flash[:error] = t('.must_be_logged')
ajax_redirect_to root_path and return
end

@note = Note.new(note_params)
# truncate the body for the title
@note.title = @note.body
Expand All @@ -13,50 +25,118 @@ def create
@note.collection = @work.collection
@note.user = current_user

respond_to do |format|
if not user_signed_in?
format.html { redirect_back fallback_location: root_path, flash: { error: t('.must_be_logged') } }
elsif @note.save
record_deed
format.json { render json: { html: render_to_string(partial: 'note.html', locals: { note: @note }, formats: [:html]) }, status: :created }
format.html { redirect_back fallback_location: @note, notice: t('.note_has_been_created') }
else
format.json { render json: @note.errors.full_messages, status: :unprocessable_entity }
format.html { redirect_back fallback_location: @note, flash: { error: t('.error_creating_note') } }
end
if @note.save
record_deed
render json: {
html: render_to_string(partial: 'note.html', locals: { note: @note }, formats: [:html]),
flash: render_to_string(partial: 'shared/flash', locals: { type: 'notice', message: t('.note_has_been_created') },
formats: [:html])
}, status: :created
else
render json: {
errors: @note.errors.full_messages,
flash: render_to_string(partial: 'shared/flash', locals: { type: 'error', message: t('.error_creating_note') })
}, status: :unprocessable_entity
end
end

def update
@note = Note.find(params[:id])
respond_to do |format|
if @note.update(note_params)
note_body = sanitize(@note.body, tags: %w(strong b em i a), attributes: %w(href))

format.json { render json: { html: simple_format(note_body) }, status: :ok }
format.html { redirect_back fallback_location: @note, notice: t('.note_has_been_updated') }
else
format.json { render json: @note.errors.full_messages, status: :unprocessable_entity }
format.html { redirect_back fallback_location: @note, flash: { error: t('.error_updating_note') } }
end
ajax_redirect_to root_path and return unless user_signed_in? && @note.user == current_user

if @note.update(note_params)
note_body = sanitize(@note.body, tags: %w(strong b em i a), attributes: %w(href))

render json: {
html: simple_format(note_body),
flash: render_to_string(partial: 'shared/flash', locals: { type: 'notice', message: t('.note_has_been_updated') },
formats: [:html])
}, status: :ok
else
render json: {
errors: @note.errors.full_messages,
flash: render_to_string(partial: 'shared/flash', locals: { type: 'error', message: t('.error_updating_note') })
}, status: :unprocessable_entity
end
end

def destroy
@note = Note.find(params[:id])
@note.destroy
respond_to do |format|
format.json { head :no_content }
format.html { redirect_back fallback_location: root_path, notice: t('.note_has_been_deleted') }

unless user_signed_in? && (@note.user == current_user || current_user.like_owner?(@note.work))
ajax_redirect_to root_path and return
end

@note.destroy

render json: {
flash: render_to_string(partial: 'shared/flash', locals: { type: 'notice', message: t('.note_has_been_deleted') })
}
end

def edit
@note = Note.find(params[:id])
def discussions
@pages = @collection.pages
.where.not(last_note_updated_at: nil)
.reorder(last_note_updated_at: :desc)
.paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
end

def show
@note = Note.find(params[:id])
private

def filtered_notes
@sorting = (params[:sort] || 'time').to_sym
@ordering = (params[:order] || 'DESC').downcase.to_sym
@ordering = [:asc, :desc].include?(@ordering) ? @ordering : :desc

if @collection.present?
notes_scope = @collection.notes.includes(:user, :work, :page)
if params[:search]
query = "%#{params[:search].to_s.downcase}%"

notes_users = User.where(id: notes_scope.select(:user_id))
.where('LOWER(users.display_name) LIKE :search', search: "%#{query}%")
notes_filter_by_user = notes_scope.where(user_id: notes_users.select(:id))

notes_filter_by_note = notes_scope.where('LOWER(notes.title) LIKE :search', search: "%#{query}%")

notes_pages = Page.where(id: notes_scope.select(:page_id))
.where('LOWER(pages.title) LIKE :search', search: "%#{query}%")
notes_filter_by_page = notes_scope.where(page_id: notes_pages.select(:id))

notes_works = Work.where(id: notes_scope.select(:work_id))
.where('LOWER(works.title) LIKE :search', search: "%#{query}%")
notes_filter_by_work = notes_scope.where(work_id: notes_works.select(:id))

notes_scope = notes_filter_by_user.or(notes_filter_by_note)
.or(notes_filter_by_page)
.or(notes_filter_by_work)
end

case @sorting
when :user
notes_scope = notes_scope.reorder("users.display_name #{@ordering}")
when :note
notes_scope = notes_scope.reorder(title: @ordering)
when :page
notes_scope = notes_scope.reorder("pages.title #{@ordering}")
when :work
notes_scope = notes_scope.reorder("works.title #{@ordering}")
else
notes_scope = notes_scope.reorder(created_at: @ordering)
end
else
notes_scope = Note.none
end

if params[:per_page] != '-1'
notes_scope = notes_scope.paginate(page: params[:page], per_page: params[:per_page] || DEFAULT_NOTES_PER_PAGE)
end
@notes = notes_scope
end

def note_params
params.require(:note).permit(:body)
end

def record_deed
Expand All @@ -72,26 +152,4 @@ def record_deed
update_search_attempt_contributions
end

def discussions
@pages = @collection.pages.where.not(last_note_updated_at: nil).reorder(last_note_updated_at: :desc).paginate :page => params[:page], :per_page => PAGES_PER_SCREEN
end

def list
respond_to do |format|
format.html
format.json {
render json: NoteDatatable.new(
params,
view_context: view_context,
collection_id: params[:collection_id]
)
}
end
end

private

def note_params
params.require(:note).permit(:body)
end
end
Loading

0 comments on commit 16ead55

Please sign in to comment.