Compare commits

..

8 Commits

13 changed files with 272 additions and 32 deletions

View File

@ -171,6 +171,7 @@ class Admin::UniversalTablesController < OrbitAdminController
def add_entry def add_entry
entry = TableEntry.new(table_entry_params) entry = TableEntry.new(table_entry_params)
entry.save entry.save
entry.fix_have_data
table = UTable.find(params[:table_entry][:u_table_id]) table = UTable.find(params[:table_entry][:u_table_id])
redirect_to admin_universal_table_new_entry_path(table) redirect_to admin_universal_table_new_entry_path(table)
end end
@ -189,14 +190,15 @@ class Admin::UniversalTablesController < OrbitAdminController
entry = TableEntry.find(params[:universal_table_id]) entry = TableEntry.find(params[:universal_table_id])
table = entry.u_table table = entry.u_table
entry.destroy entry.destroy
redirect_to admin_universal_table_path(table) redirect_to (request.referrer || admin_universal_table_path(table))
end end
def update_entry def update_entry
entry = TableEntry.find(params[:id]) entry = TableEntry.find(params[:id])
entry.update_attributes(table_entry_params) entry.update_attributes(table_entry_params)
entry.fix_have_data # when new column insert
table = entry.u_table table = entry.u_table
redirect_to admin_universal_table_path(table) redirect_to admin_universal_table_path(:id=> table.to_param, :page => params[:page])
end end
def new def new

View File

@ -3,7 +3,8 @@ class UniversalTablesController < ApplicationController
def index def index
params = OrbitHelper.params params = OrbitHelper.params
table = UTable.where(:category_id => OrbitHelper.page_categories.first).first rescue nil table = UTable.where(:category_id => OrbitHelper.page_categories.first).first rescue nil
page = Page.where(:page_id => params[:page_id]).first page = OrbitHelper.page rescue Page.where(:page_id => params[:page_id]).first
searchable_columns = []
if !table.nil? if !table.nil?
reset = "hide" reset = "hide"
csrf_value = (0...46).map { ('a'..'z').to_a[rand(26)] }.join csrf_value = (0...46).map { ('a'..'z').to_a[rand(26)] }.join
@ -16,6 +17,7 @@ class UniversalTablesController < ApplicationController
end end
query_string += "&page_no=#{params_no}" if params_no.present? query_string += "&page_no=#{params_no}" if params_no.present?
csrf_input = "<input type=\"hidden\" name=\"authenticity_token\" value=\"#{csrf_value}\">" csrf_input = "<input type=\"hidden\" name=\"authenticity_token\" value=\"#{csrf_value}\">"
have_serial_number = (page.layout != 'index1')
table_heads = table.table_columns.where(:display_in_index => true).asc(:order).collect do |tc| table_heads = table.table_columns.where(:display_in_index => true).asc(:order).collect do |tc|
field_key = tc.key field_key = tc.key
field_value = params_q[field_key] field_value = params_q[field_key]
@ -42,6 +44,9 @@ class UniversalTablesController < ApplicationController
if tc.make_categorizable if tc.make_categorizable
select_values = tc.column_entries.distinct("text.#{I18n.locale.to_s}") select_values = tc.column_entries.distinct("text.#{I18n.locale.to_s}")
form_field = "#{csrf_input}<select class=\"form-control\" name=\"q[#{field_key}]\">" form_field = "#{csrf_input}<select class=\"form-control\" name=\"q[#{field_key}]\">"
if field_value.nil?
select_values.insert(0, "")
end
select_values.each do |sv| select_values.each do |sv|
if field_value && sv == field_value if field_value && sv == field_value
selected = " selected" selected = " selected"
@ -56,6 +61,9 @@ class UniversalTablesController < ApplicationController
if tc.make_categorizable if tc.make_categorizable
select_values = tc.column_entries.distinct(:number) select_values = tc.column_entries.distinct(:number)
form_field = "#{csrf_input}<select class=\"form-control\" name=\"q[#{field_key}]\">" form_field = "#{csrf_input}<select class=\"form-control\" name=\"q[#{field_key}]\">"
if field_value.nil?
select_values.insert(0, "")
end
select_values.each do |sv| select_values.each do |sv|
if field_value && sv == field_value if field_value && sv == field_value
selected = " selected" selected = " selected"
@ -76,7 +84,9 @@ class UniversalTablesController < ApplicationController
title_class = title_class + "no-sort" if sort == "sort" title_class = title_class + "no-sort" if sort == "sort"
title_class = title_class + " no-search" if search == "hide" title_class = title_class + " no-search" if search == "hide"
{ col_class = 'col-md-4 col-xs-12'
col = {
"title" => tc.title, "title" => tc.title,
"type" => tc.type, "type" => tc.type,
"key" => field_key, "key" => field_key,
@ -85,16 +95,40 @@ class UniversalTablesController < ApplicationController
"sort" => sort, "sort" => sort,
"sort-class" => sort_class, "sort-class" => sort_class,
"sort-url" => sort_url, "sort-url" => sort_url,
"title-class" => title_class "title-class" => title_class,
"col-class" => col_class
} }
if tc.is_searchable
searchable_columns << col
end
col
end end
tablecolumns = table.table_columns.where(:display_in_index => true).asc(:order) tablecolumns = table.table_columns.where(:display_in_index => true).asc(:order)
rows = [] rows = []
entries = get_entries(params, table, page) entries = get_entries(params, table, page)
total_pages = entries.total_pages total_pages = entries.total_pages
if have_serial_number
page_no_offset = (params_no.present? ? [0, params_no.to_i - 1].max : 0)
serial_number_count = page_no_offset * OrbitHelper.page_data_count
table_heads.insert(0, {
"title" => "No.",
"type" => "",
"key" => "",
"search" => "hide",
"form-field" => "",
"sort" => "hide",
"sort-class" => "sort hide",
"sort-url" => "",
"title-class" => " no-search"
})
end
entries.each do |te| entries.each do |te|
cols = [] cols = []
sort_value = "" sort_value = ""
if have_serial_number
serial_number_count += 1
cols << {"text" => serial_number_count.to_s}
end
tablecolumns.each do |column| tablecolumns.each do |column|
ce = te.column_entries.where(:table_column_id => column.id).first rescue nil ce = te.column_entries.where(:table_column_id => column.id).first rescue nil
if !ce.nil? if !ce.nil?
@ -128,6 +162,7 @@ class UniversalTablesController < ApplicationController
reset = "" reset = ""
end end
{ {
"searchable-columns" => searchable_columns,
"head-columns" => table_heads, "head-columns" => table_heads,
"rows" => rows, "rows" => rows,
"total_pages" => total_pages, "total_pages" => total_pages,
@ -243,9 +278,9 @@ class UniversalTablesController < ApplicationController
end end
end end
if table_entry_ids.nil? if table_entry_ids.nil?
entries = TableEntry.where(:u_table_id=>table.id) entries = TableEntry.where(:u_table_id=>table.id, "have_data.#{I18n.locale}" => {"$in" => [nil, true]})
else else
entries = TableEntry.where(:id.in=> table_entry_ids) entries = TableEntry.where(:id.in=> table_entry_ids, "have_data.#{I18n.locale}" => {"$in" => [nil, true]})
end end
entries = TableEntry.sorted(entries: entries, params: params, table: table, paginated: paginated) entries = TableEntry.sorted(entries: entries, params: params, table: table, paginated: paginated)
if paginated if paginated
@ -253,9 +288,9 @@ class UniversalTablesController < ApplicationController
end end
else else
if paginated if paginated
entries = TableEntry.where(:u_table_id=>table.id).sorting(params: params,table: table,page_num: params["page_no"],per: OrbitHelper.page_data_count) entries = TableEntry.where(:u_table_id=>table.id, "have_data.#{I18n.locale}" => {"$in" => [nil, true]}).sorting(params: params,table: table,page_num: params["page_no"],per: OrbitHelper.page_data_count)
else else
entries = TableEntry.where(:u_table_id=>table.id).sorting(params: params,table: table,paginated: false) entries = TableEntry.where(:u_table_id=>table.id, "have_data.#{I18n.locale}" => {"$in" => [nil, true]}).sorting(params: params,table: table,paginated: false)
end end
end end
entries entries

View File

@ -40,6 +40,33 @@ class ColumnEntry
end end
end end
def have_data(locale)
flag = nil
case self.type
when "text"
flag = self.text_translations[locale].present?
when "integer"
flag = true
when "editor"
flag = self.content_translations[locale].present?
when "date"
flag = self.date.present?
when "period"
flag = self.period_from.present? || self.period_to.present?
when "image"
flag = self.image.present?
when "file"
flag = false
self.column_entry_files.each do |entry_file|
next unless entry_file.choose_lang_display(locale) && entry_file.file.present?
flag = true
end
else
flag = true
end
flag
end
def get_frontend_text(column) def get_frontend_text(column)
text = "" text = ""
case self.type case self.type

View File

@ -8,6 +8,7 @@ class TableColumn
field :type field :type
field :date_format, default: "YYYY/MM/DD" field :date_format, default: "YYYY/MM/DD"
field :is_link_to_show, type: Boolean, default: false field :is_link_to_show, type: Boolean, default: false
field :is_searchable, type: Boolean
field :order, type: Integer field :order, type: Integer
field :make_categorizable, type: Boolean, default: false field :make_categorizable, type: Boolean, default: false
field :default_ordered_field, type: Boolean, default: false field :default_ordered_field, type: Boolean, default: false
@ -36,4 +37,21 @@ class TableColumn
{period_from: direction,period_to: direction} {period_from: direction,period_to: direction}
end end
end end
def is_searchable
tmp = self[:is_searchable]
if tmp.nil?
case self.type
when "date", "period","image"
tmp = false
else
tmp = self.display_in_index
end
end
tmp
end
def self.filter_searchable
self.any_of({is_searchable: true}, {is_searchable:nil, display_in_index: true, :type.nin=> ["date", "period","image"]})
end
end end

View File

@ -4,6 +4,7 @@ class TableEntry
include Slug include Slug
attr_accessor :sort_value attr_accessor :sort_value
field :have_data, type: Boolean, localize: true
field :sort_number, type: Integer field :sort_number, type: Integer
field :view_count, type: Integer, default: 0 field :view_count, type: Integer, default: 0
@ -12,11 +13,31 @@ class TableEntry
accepts_nested_attributes_for :column_entries, :allow_destroy => true accepts_nested_attributes_for :column_entries, :allow_destroy => true
I18n.available_locales.each do |locale|
index({have_data: 1}, { collation: {locale: locale.to_s}, unique: false, background: true })
end
before_save do before_save do
if self[:sort_number].nil? if self[:sort_number].nil?
sort_number_to_set = self.class.where(u_table_id: self.u_table_id).order_by(sort_number: :desc).first.sort_number rescue 0 other_record = self.class.where(:u_table_id=> self.u_table_id, :id.ne=> self.id).order_by(sort_number: :desc).first
self.sort_number = sort_number_to_set + 1 sort_number_to_set = other_record ? other_record.sort_number : 0
self.sort_number = sort_number_to_set.to_i + 1
end end
self.get_have_data
end
def fix_have_data
have_data_translations = self.get_have_data
self.class.where(:id=> self.id).update_all(have_data_translations.map{|l, v| ["have_data.#{l}", v]}.to_h)
end
def get_have_data
searchable_field_ids = TableColumn.filter_searchable.where(u_table_id: self.u_table_id).pluck(:id)
searchable_column_entries = self.column_entries.where(:table_column_id.in=> searchable_field_ids).to_a
self.have_data_translations = I18n.available_locales.map do |locale|
flag = searchable_column_entries.detect{|ce| ce.have_data(locale)}.present?
[locale.to_s, flag]
end.to_h
end end
def self.u_table def self.u_table

View File

@ -77,6 +77,9 @@
<label class="checkbox inline attributes-checkbox"> <label class="checkbox inline attributes-checkbox">
<%= f.check_box :make_categorizable %> Categorizable <%= f.check_box :make_categorizable %> Categorizable
</label> </label>
<label class="checkbox inline attributes-checkbox ">
<%= f.check_box :is_searchable %> Searchable
</label>
</span> </span>
<% select_values = UTable::DATE_FORMATS.collect{|ft| [ft.upcase,ft]} %> <% select_values = UTable::DATE_FORMATS.collect{|ft| [ft.upcase,ft]} %>
<label class="checkbox date_format inline attributes-checkbox <%= column.type == "date" || column.type == "period" ? "" : "hide" %>"> <label class="checkbox date_format inline attributes-checkbox <%= column.type == "date" || column.type == "period" ? "" : "hide" %>">

View File

@ -1,6 +1,7 @@
<%= form_for @entry, url: "/admin/universal_tables/update_entry", html: {class: "form-horizontal main-forms"} do |f| %> <%= form_for @entry, url: "/admin/universal_tables/update_entry", html: {class: "form-horizontal main-forms"} do |f| %>
<fieldset> <fieldset>
<input value="<%= @entry.id %>" type="hidden" name="id"> <%= f.hidden_field_tag 'id', f.object.id %>
<%= f.hidden_field_tag 'page', params[:page] %>
<%= render :partial => "entry_form", :locals => {:f => f} %> <%= render :partial => "entry_form", :locals => {:f => f} %>
</fieldset> </fieldset>
<% end %> <% end %>

View File

@ -68,7 +68,7 @@
<% if index == 0 && can_edit %> <% if index == 0 && can_edit %>
<div class="quick-edit"> <div class="quick-edit">
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li><a href="<%= admin_universal_table_edit_entry_path(entry) %>"><%= t(:edit) %></a></li> <li><a href="<%= admin_universal_table_edit_entry_path(:universal_table_id=> entry.to_param, :page => params[:page]) %>"><%= t(:edit) %></a></li>
<li><a href="<%= admin_universal_table_delete_entry_path(entry.id) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li> <li><a href="<%= admin_universal_table_delete_entry_path(entry.id) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li>
</ul> </ul>
</div> </div>

View File

@ -1,5 +1,18 @@
Rails.application.routes.draw do Rails.application.routes.draw do
if ENV['worker_num']=='0' && File.basename($0) != 'rake' && !Rails.const_defined?('Console')
Thread.new do
stored_flag = "utf1"
need_update = Site.pluck(:tmp_flags).flatten.compact.exclude?(stored_flag)
if need_update
TableEntry.all.to_a.each do |te|
te.fix_have_data
end
Site.update_all("$push"=>{"tmp_flags"=> stored_flag})
end
end
end
locales = Site.first.in_use_locales rescue I18n.available_locales locales = Site.first.in_use_locales rescue I18n.available_locales
scope "(:locale)", locale: Regexp.new(locales.join("|")) do scope "(:locale)", locale: Regexp.new(locales.join("|")) do

View File

@ -84,7 +84,7 @@
<div>{{total_entries}}</div> <div>{{total_entries}}</div>
<div>{{export_button}}</div> <div>{{export_button}}</div>
{{pagination_goes_here}} {{pagination_goes_here}}
<script> <script type="text/javascript">
$('.universal-table-index th').eq(1).attr('class', 'desktop tablet-l tablet-p'); $('.universal-table-index th').eq(1).attr('class', 'desktop tablet-l tablet-p');
$('.universal-table-index th').filter(':gt(1)').attr('class', 'desktop tablet-l tablet-p mobile-l'); $('.universal-table-index th').filter(':gt(1)').attr('class', 'desktop tablet-l tablet-p mobile-l');
$('.universal-table-index').each(function(){ $('.universal-table-index').each(function(){
@ -100,6 +100,9 @@
}); });
} }
}); });
$(document).on('click', '.universal-table-index .dropdown-menu', function (e) {
e.stopPropagation();
});
</script> </script>
<style> <style>
.universal-table-index.dtr-inline.collapsed td.dtr-control{ .universal-table-index.dtr-inline.collapsed td.dtr-control{

View File

@ -7,10 +7,11 @@
padding: 8px 0 0 0; padding: 8px 0 0 0;
display: inline; display: inline;
margin-right: 5px; margin-right: 5px;
color: #888; color: #fff;
} }
.universal-dropdown { .universal-dropdown {
display: inline-block; display: inline-block;
color: gray;
} }
a.universal-btn { a.universal-btn {
vertical-align: baseline; vertical-align: baseline;
@ -36,26 +37,14 @@
.universal-table-index tbody { .universal-table-index tbody {
counter-reset: item; counter-reset: item;
} }
.universal-table-index tbody > tr > td:first-child:before {
content: counter(item);
counter-increment: item;
}
.universal-table-index thead > tr > th:first-child:before {
content: "No.";
}
.universal-table-index thead > tr > th:first-child { .universal-table-index thead > tr > th:first-child {
width: 4em; width: 4em;
} }
.universal-th-icon { .universal-th-icon {
border: 1px solid #eee; border: 1px solid #eee;
padding: 5px 8px; padding: 5px 8px;
margin-right: 5px; margin-right: 5px;
color: gray; color: #fff;
cursor: pointer; cursor: pointer;
} }
.universal-th-text.no-sort.no-search { .universal-th-text.no-sort.no-search {
@ -102,7 +91,9 @@
<div>{{total_entries}}</div> <div>{{total_entries}}</div>
<div>{{export_button}}</div> <div>{{export_button}}</div>
{{pagination_goes_here}} {{pagination_goes_here}}
<script>
$('.universal-table-index thead tr').prepend('<th></th>') <script type="text/javascript">
$('.universal-table-index tbody tr').prepend('<td></td>') $(document).on('click', '.universal-table-index .dropdown-menu', function (e) {
</script> e.stopPropagation();
});
</script>

View File

@ -0,0 +1,118 @@
<style>
.universal-dropdown-menu {
padding: 15px 18px;
white-space: nowrap;
}
.universal-th-text {
padding: 8px 0 0 0;
display: inline;
margin-right: 5px;
color: #888;
}
.universal-dropdown {
display: inline-block;
}
a.universal-btn {
vertical-align: baseline;
color: #fff;
}
.universal-table-index {
border-collapse: collapse;
border: 1px solid #eee;
table-layout: fixed;
word-wrap: break-word;
}
.universal-table-index h3 {
float: left;
margin: 0;
}
.universal-table-index.table td{
padding: 15px 18px;
}
.universal-table-index thead th:last-child .dropdown-menu {
left: auto;
right: 0;
}
.universal-table-index tbody {
counter-reset: item;
}
.universal-th-icon {
border: 1px solid #eee;
padding: 5px 8px;
margin-right: 5px;
color: gray;
cursor: pointer;
}
.universal-th-text.no-sort.no-search {
position: relative;
top: -6px;
}
.image-preview {
width: 120px;
}
</style>
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
<table class="table table-hover table-striped universal-table-index">
<div class="searchbtn">
<div class="ken-click">
<div class="searchbtn2 pull-right"><i class="fa-solid fa-magnifying-glass"></i>查詢</div>
<a href="{{url}}" class="universal-btn btn btn-info pull-right {{reset}}"><i class="fa fa-refresh"></i> Reset</a>
</div>
</div>
<div class="searchbox">
<div class="theadsearch2">
<div class="row col-md-11 col-xs-12" data-list="searchable-columns" data-level="0">
<div class="{{col-class}}">
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
<div class="universal-th-text {{title-class}}">{{title}}</div>
<div class="dropdown universal-dropdown {{search}}">
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
<div class="form-group">
{{form-field}}
<input type="hidden" value="{{key}}" name="column" >
</div>
</div>
</div>
</div>
</div>
<div class="col-md-1 col-xs-12 submit-btn-wrap">
<button class="btn btn-primary pull-right" type="submit" class="btn btn-default">Go</button>
</div>
</div>
</div>
<caption>
<h3>{{table-name}}</h3>
</caption>
<thead class="theadsearch">
<tr data-list="head-columns" data-level="0">
<th class="col-md-3">
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
<div class="universal-th-text {{title-class}}">{{title}}</div>
<div class="dropdown universal-dropdown {{search}}">
<button class="btn btn-md" id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-search"></i>
<span class="caret"></span>
</button>
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
<div class="form-group">
{{form-field}}
<input type="hidden" value="{{key}}" name="column" >
</div>
<button class="btn btn-primary" type="submit" class="btn btn-default">Go</button>
</form>
</div>
</div>
</th>
</tr>
</thead>
<tbody data-level="0" data-list="rows">
<tr data-level="1" data-list="columns">
<td>{{text}}</td>
</tr>
</tbody>
</table>
</form>
<div>{{total_entries}}</div>
<div>{{export_button}}</div>
{{pagination_goes_here}}

View File

@ -14,6 +14,14 @@
"zh_tw" : "2. 含序號表格列表", "zh_tw" : "2. 含序號表格列表",
"en" : "2. Index Table with serial number" "en" : "2. Index Table with serial number"
}, },
"thumbnail" : "thumb.png"
},
{
"filename" : "index3",
"name" : {
"zh_tw" : "3. 含序號表格列表 + 多欄位搜尋",
"en" : "3. Index Table with serial number + Multiple Field Search"
},
"thumbnail" : "thumb.png", "thumbnail" : "thumb.png",
"default": true "default": true
} }