mensa 0.1.0 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/gem-push.yml +11 -0
- data/.gitignore +12 -0
- data/Gemfile +10 -8
- data/Gemfile.lock +299 -0
- data/MIT-LICENSE +20 -0
- data/Procfile +2 -0
- data/README.md +76 -18
- data/Rakefile +5 -13
- data/app/assets/config/mensa_manifest.js +5 -0
- data/app/assets/images/mensa/.keep +0 -0
- data/app/assets/stylesheets/mensa/application.css +11 -0
- data/app/components/mensa/add_filter/component.css +13 -0
- data/app/components/mensa/add_filter/component.html.slim +16 -0
- data/app/components/mensa/add_filter/component.rb +13 -0
- data/app/components/mensa/add_filter/component_controller.js +84 -0
- data/app/components/mensa/application_component.rb +12 -0
- data/app/components/mensa/cell/component.css +0 -0
- data/app/components/mensa/cell/component.html.slim +1 -0
- data/app/components/mensa/cell/component.rb +15 -0
- data/app/components/mensa/control_bar/component.css +5 -0
- data/app/components/mensa/control_bar/component.html.slim +15 -0
- data/app/components/mensa/control_bar/component.rb +21 -0
- data/app/components/mensa/filters/component.css +14 -0
- data/app/components/mensa/filters/component.html.slim +14 -0
- data/app/components/mensa/filters/component.rb +13 -0
- data/app/components/mensa/filters/component_controller.js +14 -0
- data/app/components/mensa/header/component.css +54 -0
- data/app/components/mensa/header/component.html.slim +8 -0
- data/app/components/mensa/header/component.rb +19 -0
- data/app/components/mensa/row_action/component.css +0 -0
- data/app/components/mensa/row_action/component.html.slim +6 -0
- data/app/components/mensa/row_action/component.rb +19 -0
- data/app/components/mensa/search/component.css +13 -0
- data/app/components/mensa/search/component.html.slim +17 -0
- data/app/components/mensa/search/component.rb +13 -0
- data/app/components/mensa/search/component_controller.js +62 -0
- data/app/components/mensa/table/component.css +0 -0
- data/app/components/mensa/table/component.html.slim +7 -0
- data/app/components/mensa/table/component.rb +16 -0
- data/app/components/mensa/table/component_controller.js +72 -0
- data/app/components/mensa/table_row/component.css +0 -0
- data/app/components/mensa/table_row/component.html.slim +6 -0
- data/app/components/mensa/table_row/component.rb +19 -0
- data/app/components/mensa/view/component.css +82 -0
- data/app/components/mensa/view/component.html.slim +17 -0
- data/app/components/mensa/view/component.rb +16 -0
- data/app/components/mensa/views/component.css +13 -0
- data/app/components/mensa/views/component.html.slim +18 -0
- data/app/components/mensa/views/component.rb +14 -0
- data/app/controllers/concerns/.keep +0 -0
- data/app/controllers/mensa/application_controller.rb +4 -0
- data/app/controllers/mensa/tables/filters_controller.rb +29 -0
- data/app/controllers/mensa/tables_controller.rb +37 -0
- data/app/helpers/mensa/application_helper.rb +5 -0
- data/app/helpers/mensa/tables_helper.rb +5 -0
- data/app/javascript/mensa/application.js +3 -0
- data/app/javascript/mensa/controllers/alert_controller.js +7 -0
- data/app/javascript/mensa/controllers/application.js +8 -0
- data/app/javascript/mensa/controllers/application_controller.js +57 -0
- data/app/javascript/mensa/controllers/index.js +24 -0
- data/app/jobs/mensa/application_job.rb +7 -0
- data/app/jobs/mensa/export_job.rb +91 -0
- data/app/models/concerns/.keep +0 -0
- data/app/models/mensa/application_record.rb +5 -0
- data/app/models/mensa/table_view.rb +5 -0
- data/app/tables/mensa/action.rb +26 -0
- data/app/tables/mensa/base.rb +103 -0
- data/app/tables/mensa/cell.rb +50 -0
- data/app/tables/mensa/column.rb +85 -0
- data/app/tables/mensa/config/action_dsl.rb +12 -0
- data/app/tables/mensa/config/column_dsl.rb +23 -0
- data/app/tables/mensa/config/dsl_logic.rb +90 -0
- data/app/tables/mensa/config/filter_dsl.rb +8 -0
- data/app/tables/mensa/config/render_dsl.rb +8 -0
- data/app/tables/mensa/config/table_dsl.rb +71 -0
- data/app/tables/mensa/config_readers.rb +13 -0
- data/app/tables/mensa/filter.rb +31 -0
- data/app/tables/mensa/row.rb +36 -0
- data/app/tables/mensa/scope.rb +99 -0
- data/app/views/layouts/mensa/application.html.slim +11 -0
- data/app/views/mensa/tables/filters/show.turbo_stream.slim +8 -0
- data/app/views/mensa/tables/index.html.slim +93 -0
- data/app/views/mensa/tables/show.html.slim +5 -0
- data/app/views/mensa/tables/show.turbo_stream.slim +5 -0
- data/bin/importmap +4 -0
- data/bin/rails +14 -0
- data/config/importmap.rb +8 -0
- data/config/locales/en.yml +8 -0
- data/config/locales/nl.yml +11 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20240201184752_create_mensa_table_views.rb +15 -0
- data/lib/generators/mensa/install_generator.rb +24 -0
- data/lib/generators/mensa/tailwind_config_generator.rb +24 -0
- data/lib/generators/mensa/templates/config/initializers/mensa.rb +22 -0
- data/lib/mensa/configuration.rb +90 -0
- data/lib/mensa/engine.rb +37 -0
- data/lib/mensa/version.rb +1 -3
- data/lib/mensa.rb +24 -5
- data/lib/tasks/mensa_tasks.rake +13 -0
- data/mensa.gemspec +42 -0
- data/package.json +10 -0
- data/vendor/javascript/@rails--request.js.js +2 -0
- data/yarn.lock +837 -0
- metadata +245 -18
- data/.rubocop.yml +0 -13
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -84
- data/LICENSE.txt +0 -21
- data/sig/mensa.rbs +0 -4
@@ -0,0 +1,29 @@
|
|
1
|
+
module Mensa
|
2
|
+
module Tables
|
3
|
+
class FiltersController < ::ApplicationController
|
4
|
+
layout false
|
5
|
+
|
6
|
+
class Filter
|
7
|
+
include ActiveModel::Model
|
8
|
+
attr_accessor :value
|
9
|
+
end
|
10
|
+
|
11
|
+
def index
|
12
|
+
config = {}.merge(params.permit(:id, :page, :table_id, :target, :table_view_id, :turbo_frame_id, order: {}, filters: {}).to_h)
|
13
|
+
@table = Mensa.for_name(params[:table_id], config)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the filter information on the column-name
|
17
|
+
def show
|
18
|
+
config = {}.merge(params.permit(:id, :page, :table_id, :target, :table_view_id, :turbo_frame_id, order: {}, filters: {}).to_h)
|
19
|
+
@table = Mensa.for_name(params[:table_id], config)
|
20
|
+
@table.original_view_context = helpers
|
21
|
+
@column = @table.column(params[:id])
|
22
|
+
respond_to do |format|
|
23
|
+
format.turbo_stream
|
24
|
+
format.html
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Mensa
|
2
|
+
class TablesController < ::ApplicationController
|
3
|
+
layout :decide_layout
|
4
|
+
|
5
|
+
def index
|
6
|
+
render layout: 'mensa/application'
|
7
|
+
end
|
8
|
+
|
9
|
+
def show
|
10
|
+
config = if params[:table_view_id]
|
11
|
+
@view = Mensa::TableView.find_by(table_name: params[:id], id: params[:table_view_id])
|
12
|
+
@view&.data || {}
|
13
|
+
else
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
|
17
|
+
config = config.merge(params.permit(:format, :query, :id, :page, :table_view_id, :turbo_frame_id, order: {}, filters: {}).to_h)
|
18
|
+
|
19
|
+
@table = Mensa.for_name(params[:id], config)
|
20
|
+
@table.table_view = @view
|
21
|
+
@table.original_view_context = helpers
|
22
|
+
|
23
|
+
respond_to do |format|
|
24
|
+
format.turbo_stream
|
25
|
+
format.html
|
26
|
+
format.xlsx do
|
27
|
+
Mensa::ExportJob.perform_async(current_user.id, params[:id])
|
28
|
+
head 200
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def decide_layout
|
34
|
+
return false if params[:turbo_frame_id]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
// application_controller.js
|
2
|
+
import { Controller } from '@hotwired/stimulus'
|
3
|
+
|
4
|
+
export default class ApplicationController extends Controller {
|
5
|
+
connect () {
|
6
|
+
this.element[this.identifier] = this
|
7
|
+
}
|
8
|
+
|
9
|
+
getController (element, identifier) {
|
10
|
+
return this.application.getControllerForElementAndIdentifier(element, identifier)
|
11
|
+
}
|
12
|
+
|
13
|
+
triggerEvent (el, name, data) {
|
14
|
+
let event
|
15
|
+
if (typeof window.CustomEvent === 'function') {
|
16
|
+
event = new CustomEvent(name, { detail: data, cancelable: true, bubbles: true })
|
17
|
+
} else {
|
18
|
+
event = document.createEvent('CustomEvent')
|
19
|
+
event.initCustomEvent(name, true, true, data)
|
20
|
+
}
|
21
|
+
el.dispatchEvent(event)
|
22
|
+
}
|
23
|
+
|
24
|
+
elementScrolled (element) {
|
25
|
+
if (element.scrollHeight - Math.round(element.scrollTop) === element.clientHeight) {
|
26
|
+
return true
|
27
|
+
}
|
28
|
+
return false
|
29
|
+
}
|
30
|
+
|
31
|
+
debouncedHover (element, timeout, handler) {
|
32
|
+
var timeoutId = null
|
33
|
+
element.addEventListener(marker, 'mouseover', function () {
|
34
|
+
timeoutId = setTimeout(handler, timeout)
|
35
|
+
})
|
36
|
+
|
37
|
+
element.addEventListener(marker, 'mouseout', function () {
|
38
|
+
clearTimeout(timeoutId)
|
39
|
+
})
|
40
|
+
}
|
41
|
+
|
42
|
+
get ourUrl () {
|
43
|
+
// This requires the mensaTableOutlet
|
44
|
+
let turboFrame = this.mensaTableOutlet?.turboFrameTarget
|
45
|
+
if(!turboFrame) {
|
46
|
+
turboFrame = this.turboFrameTarget
|
47
|
+
}
|
48
|
+
let url
|
49
|
+
|
50
|
+
if (turboFrame && turboFrame.getAttribute('src')) {
|
51
|
+
url = new URL(turboFrame.getAttribute('src'))
|
52
|
+
} else {
|
53
|
+
url = new URL(window.location.href)
|
54
|
+
}
|
55
|
+
return url
|
56
|
+
}
|
57
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { application } from "mensa/controllers/application"
|
2
|
+
|
3
|
+
// import AddFilterComponentController from 'components/add_filter/component_controller';
|
4
|
+
// application.register('mensa-add-filter', AddFilterComponentController)
|
5
|
+
|
6
|
+
import AddFilterComponentController from "mensa/components/add_filter/component_controller";
|
7
|
+
application.register("mensa-add-filter", AddFilterComponentController);
|
8
|
+
|
9
|
+
import FiltersComponentController from "mensa/components/filters/component_controller";
|
10
|
+
application.register("mensa-filters", FiltersComponentController);
|
11
|
+
|
12
|
+
import SearchComponentController from "mensa/components/search/component_controller";
|
13
|
+
application.register("mensa-search", SearchComponentController);
|
14
|
+
|
15
|
+
import TableComponentController from 'mensa/components/table/component_controller'
|
16
|
+
application.register("mensa-table", TableComponentController);
|
17
|
+
|
18
|
+
// Eager load all controllers defined in the import map under controllers/**/*_controller
|
19
|
+
// import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
|
20
|
+
// eagerLoadControllersFrom("controllers", application)
|
21
|
+
|
22
|
+
// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
|
23
|
+
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
|
24
|
+
// lazyLoadControllersFrom("controllers", application)
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Mensa
|
2
|
+
class ExportJob < ApplicationJob
|
3
|
+
queue_as :default
|
4
|
+
|
5
|
+
# export_started: lambda do |user_id, table_name|
|
6
|
+
# end,
|
7
|
+
# export_complete: lambda do |user_id, table_name, attachment|
|
8
|
+
# end
|
9
|
+
|
10
|
+
def perform(user_id, table_name)
|
11
|
+
table = Mensa.for_name(table_name)
|
12
|
+
context = Mensa.config.callbacks[:export_started].call(user_id, table_name)
|
13
|
+
|
14
|
+
styles = []
|
15
|
+
default_style = { b: true, bg_color: '3B82F6', fg_color: 'FFFFFF', border: { style: :thin, color: '000000' }, sz: 8,
|
16
|
+
alignment: { vertical: :bottom, horizontal: :left } }
|
17
|
+
|
18
|
+
p = Axlsx::Package.new
|
19
|
+
p.use_shared_strings = true
|
20
|
+
|
21
|
+
wb = p.workbook
|
22
|
+
|
23
|
+
wrap_text = wb.styles.add_style({ format_code: "@", alignment: { horizontal: :left, vertical: :top, wrap_text: true } })
|
24
|
+
nowrap_text = wb.styles.add_style({ format_code: "@", alignment: { horizontal: :left, vertical: :top, wrap_text: false } })
|
25
|
+
number_format = wb.styles.add_style format_code: '#'
|
26
|
+
datetime_format = wb.styles.add_style format_code: "dddd, d mmmm yyyy hh:mm:ss"
|
27
|
+
date_format = wb.styles.add_style format_code: "dddd, d mmmm yyyy"
|
28
|
+
|
29
|
+
# custom_styles = ActionTable.config.format_config.each_with_object({}) { |(key, value), hash| hash[key] = value[:xlsx_style] if value.key?(:xlsx_style) }
|
30
|
+
custom_style = {}
|
31
|
+
|
32
|
+
wb.add_worksheet(name: table_name.first(31)) do |sheet|
|
33
|
+
column_widths = []
|
34
|
+
# TODO: Separate display columns for export?
|
35
|
+
table.display_columns.map.with_index do |column, index|
|
36
|
+
styles[index] = sheet.styles.add_style(default_style) #.merge(column.export_style || {}))
|
37
|
+
# width = column.export_style.delete(:width)
|
38
|
+
# column_widths[index] = width if width
|
39
|
+
end
|
40
|
+
|
41
|
+
first_row = sheet.add_row(table.display_columns.map { |c| c.human_name }, style: styles, height: 28)
|
42
|
+
|
43
|
+
first_cell = first_row.first
|
44
|
+
last_row = first_row
|
45
|
+
|
46
|
+
table.export_rows.each do |row|
|
47
|
+
# next if row.reject { |column, value| value.nil? || value == '' }.blank?
|
48
|
+
#
|
49
|
+
# row_types = :string #row.map(&:second)
|
50
|
+
# row_values = row.map(&:third)
|
51
|
+
# options = row.map(&:last)
|
52
|
+
#
|
53
|
+
# row_styles = options.map.with_index { |option, i|
|
54
|
+
# type = row_types[i]
|
55
|
+
# value = row_values[i]
|
56
|
+
# export_style = option.dig(:export, :xlsx_style)
|
57
|
+
#
|
58
|
+
# if export_style.present?
|
59
|
+
# wb.styles.add_style(export_style)
|
60
|
+
# elsif custom_styles[type].present?
|
61
|
+
# wb.styles.add_style(custom_styles[type])
|
62
|
+
# elsif [:integer, :float, :decimal, :number].include?(type)
|
63
|
+
# number_format
|
64
|
+
# elsif [:datetime, :time, :timestamp].include?(type)
|
65
|
+
# datetime_format
|
66
|
+
# elsif type == :date
|
67
|
+
# date_format
|
68
|
+
# elsif value.is_a?(Axlsx::RichText) || type == :text
|
69
|
+
# wrap_text
|
70
|
+
# else
|
71
|
+
# nowrap_text
|
72
|
+
# end
|
73
|
+
# }
|
74
|
+
values = table.display_columns.map { |column| row.value(column) }
|
75
|
+
row_styles = table.display_columns.map { |column| nowrap_text }
|
76
|
+
last_row = sheet.add_row(values, style: row_styles)
|
77
|
+
end
|
78
|
+
|
79
|
+
last_cell = last_row.last
|
80
|
+
|
81
|
+
sheet.column_widths(*column_widths)
|
82
|
+
sheet.auto_filter = Axlsx.cell_range([first_cell, last_cell], false)
|
83
|
+
end
|
84
|
+
|
85
|
+
attachment = { io: p.to_stream,
|
86
|
+
content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', filename: "#{table_name}_export_#{Time.current.strftime("%Y-%m-%d-%H:%M:%S")}.xlsx" }
|
87
|
+
|
88
|
+
Mensa.config.callbacks[:export_completed].call(user_id, table_name, context, attachment)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mensa
|
4
|
+
# Provide additional links at the end of the row, with an icon, link and a name
|
5
|
+
#
|
6
|
+
# action :delete do
|
7
|
+
# link { |contact| delete_contact_path(contact) }
|
8
|
+
# link_attributes "data-turbo-method" => "delete"
|
9
|
+
# icon "fa-xmark"
|
10
|
+
# end
|
11
|
+
class Action
|
12
|
+
include ConfigReaders
|
13
|
+
attr_reader :name, :table, :config
|
14
|
+
|
15
|
+
def initialize(name, config:, table:)
|
16
|
+
@name = name
|
17
|
+
@table = table
|
18
|
+
@config = config
|
19
|
+
end
|
20
|
+
|
21
|
+
config_reader :link
|
22
|
+
config_reader :link_attributes
|
23
|
+
config_reader :icon
|
24
|
+
config_reader :show
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Mensa
|
2
|
+
class Base
|
3
|
+
include Pagy::Backend
|
4
|
+
include ConfigReaders
|
5
|
+
include Scope
|
6
|
+
|
7
|
+
attr_writer :original_view_context
|
8
|
+
attr_accessor :component, :name, :table_view
|
9
|
+
attr_reader :config, :params
|
10
|
+
|
11
|
+
config_reader :model
|
12
|
+
config_reader :link
|
13
|
+
config_reader :supports_views?
|
14
|
+
config_reader :view_condensed?
|
15
|
+
config_reader :view_condensed_toggle?
|
16
|
+
config_reader :view_columns_sorting?
|
17
|
+
config_reader :show_header?
|
18
|
+
|
19
|
+
def initialize(config = {})
|
20
|
+
@params = config.deep_symbolize_keys
|
21
|
+
@config = self.class.definition.merge(@params || {})
|
22
|
+
end
|
23
|
+
|
24
|
+
def column_order
|
25
|
+
config[:column_order] || config[:columns]&.keys
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns all columns
|
29
|
+
def columns
|
30
|
+
return @columns if @columns
|
31
|
+
|
32
|
+
@columns ||= column_order.map { |column_name| Mensa::Column.new(column_name, config: config.dig(:columns, column_name), table: self) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a column by name
|
36
|
+
# @param [String] name
|
37
|
+
def column(name)
|
38
|
+
columns.find { |c| c.name == name.to_sym }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the columns to be displayed
|
42
|
+
def display_columns
|
43
|
+
@display_columns ||= columns.select(&:visible?).reject(&:internal?)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the rows to be displayed
|
47
|
+
def rows
|
48
|
+
paged_scope.map { |row| Mensa::Row.new(self, row) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def export_rows
|
52
|
+
ordered_scope.map { |row| Mensa::Row.new(self, row) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def actions?
|
56
|
+
config[:actions].present?
|
57
|
+
end
|
58
|
+
|
59
|
+
def actions
|
60
|
+
return @actions if @actions
|
61
|
+
|
62
|
+
@actions ||= config[:actions].keys.map { |action_name| Mensa::Action.new(action_name, config: config.dig(:actions, action_name), table: self) }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the current path with configuration
|
66
|
+
def path(order: {}, turbo_frame_id: nil, table_view_id: nil)
|
67
|
+
# FIXME: if someone doesn't use as: :mensa in the routes, it breaks
|
68
|
+
original_view_context.mensa.table_path(name, order: order_hash(order), turbo_frame_id: turbo_frame_id, table_view_id: table_view_id)
|
69
|
+
end
|
70
|
+
|
71
|
+
def menu
|
72
|
+
Satis::Menus::Builder.build([:table, :view_menu]) do |m|
|
73
|
+
m.item :export, icon: "fal fa-file-export", link: nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def all_views
|
78
|
+
[Mensa::TableView.new(name: I18n.t('.mensa.views.all'))] + TableView.where(table_name: name).where(user: [nil, Current.user])
|
79
|
+
end
|
80
|
+
|
81
|
+
def active_filters
|
82
|
+
(config[:filters] || {}).map { |column_name, value| Mensa::Filter.new(value, column: column(column_name), config: config.dig(:filters, column_name), table: self) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def table_id
|
86
|
+
return @table_id if @table_id
|
87
|
+
|
88
|
+
@table_id = params[:turbo_frame_id] || "#{name.to_s.gsub("/", "__")}-#{SecureRandom.base36}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def original_view_context
|
92
|
+
@original_view_context || component.original_view_context
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
class << self
|
98
|
+
def definition(&)
|
99
|
+
@definition ||= Mensa::Config::TableDsl.new(self.name, &).config
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mensa
|
4
|
+
class Cell
|
5
|
+
include ActionView::Helpers::SanitizeHelper
|
6
|
+
include ::ApplicationHelper
|
7
|
+
include ActionView::Helpers::TagHelper
|
8
|
+
include ActionView::Helpers::UrlHelper
|
9
|
+
include Rails.application.routes.url_helpers
|
10
|
+
|
11
|
+
attr_reader :column, :row
|
12
|
+
|
13
|
+
def initialize(row:, column:)
|
14
|
+
@row = row
|
15
|
+
@column = column
|
16
|
+
end
|
17
|
+
|
18
|
+
def value
|
19
|
+
@value ||= row.value(column)
|
20
|
+
end
|
21
|
+
|
22
|
+
def render(format)
|
23
|
+
proc = column.config.dig(:render, format.to_sym)
|
24
|
+
if proc
|
25
|
+
row.table.original_view_context.instance_exec(row.record, &proc)
|
26
|
+
else
|
27
|
+
send("to_#{format}".to_sym)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def to_html
|
34
|
+
case value
|
35
|
+
when NilClass
|
36
|
+
''
|
37
|
+
when TrueClass
|
38
|
+
content_tag(:i, '', class: 'fa fa-check')
|
39
|
+
when FalseClass
|
40
|
+
content_tag(:i, '', class: 'fa fa-xmark')
|
41
|
+
when Date
|
42
|
+
respond_to?(:dt) ? dt(value) : value.strftime('%d.%m.%Y')
|
43
|
+
when Time, DateTime
|
44
|
+
respond_to?(:ln) ? ln(value) : value.strftime('%d-%m-%Y %H:%M:%S')
|
45
|
+
else
|
46
|
+
column.sanitize? ? sanitize(value.to_s) : value.to_s.html_safe
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mensa
|
4
|
+
class Column
|
5
|
+
include ConfigReaders
|
6
|
+
attr_reader :name, :table, :config
|
7
|
+
|
8
|
+
def initialize(name, config:, table:)
|
9
|
+
@name = name
|
10
|
+
@table = table
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
config_reader :sortable?
|
15
|
+
config_reader :sanitize?
|
16
|
+
config_reader :visible?
|
17
|
+
config_reader :internal?
|
18
|
+
config_reader :method # When a method needs to be called on the model, slow!
|
19
|
+
|
20
|
+
def sort_direction
|
21
|
+
table.config.dig(:order, name)&.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def next_sort_direction
|
25
|
+
if sort_direction == :asc
|
26
|
+
:desc
|
27
|
+
elsif sort_direction == :desc
|
28
|
+
nil
|
29
|
+
else
|
30
|
+
:asc
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def attribute
|
35
|
+
return @attribute if @attribute
|
36
|
+
|
37
|
+
@attribute = if config[:attribute].present?
|
38
|
+
"#{config[:attribute]} AS #{name}"
|
39
|
+
elsif table.model.column_names.include? name.to_s
|
40
|
+
name.to_s
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def attribute_for_condition
|
47
|
+
return @attribute_for_condition if @attribute_for_condition
|
48
|
+
|
49
|
+
@attribute_for_condition = if config[:attribute].present?
|
50
|
+
config[:attribute]
|
51
|
+
elsif table.model.column_names.include? name.to_s
|
52
|
+
name.to_s
|
53
|
+
else
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def filter?
|
59
|
+
config.key?(:filter)
|
60
|
+
end
|
61
|
+
|
62
|
+
def filter
|
63
|
+
return unless filter?
|
64
|
+
|
65
|
+
@filter ||= Mensa::Filter.new(nil, column: self, config: config[:filter] || {}, table: table)
|
66
|
+
end
|
67
|
+
|
68
|
+
def human_name
|
69
|
+
if table.model < ActiveRecord::Base
|
70
|
+
table.model.human_attribute_name name
|
71
|
+
else
|
72
|
+
name.to_s.humanize
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def menu
|
77
|
+
Satis::Menus::Builder.build(:filter_menu, event: 'click') do |m|
|
78
|
+
if sortable?
|
79
|
+
m.item :sort_ascending, icon: 'fa-solid fa-arrow-up-short-wide'.freeze, link: table.path(order: { name => :asc }), link_attributes: { "data-turbo-frame": "_self" }
|
80
|
+
m.item :sort_descending, icon: 'fa-solid fa-arrow-down-wide-short'.freeze, link: table.path(order: { name => :asc }), link_attributes: { "data-turbo-frame": "_self" }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mensa::Config
|
4
|
+
class ColumnDsl
|
5
|
+
include DslLogic
|
6
|
+
|
7
|
+
option :sortable, default: true
|
8
|
+
option :sanitize, default: true
|
9
|
+
# Allows for sql-parts too
|
10
|
+
#
|
11
|
+
# attribute 'EXTRACT(YEAR FROM AGE(born_on))::int as age'
|
12
|
+
#
|
13
|
+
option :attribute
|
14
|
+
# Internal columns will never be shown, but are there to be selected, to be used in methods
|
15
|
+
# Mensa doesn't select the whole records, to only select what we need
|
16
|
+
option :internal
|
17
|
+
option :method
|
18
|
+
|
19
|
+
option :visible, default: :true
|
20
|
+
dsl_option :render, Mensa::Config::RenderDsl
|
21
|
+
dsl_option :filter, Mensa::Config::FilterDsl
|
22
|
+
end
|
23
|
+
end
|