drg_cms 0.6.1.9 → 0.7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +260 -0
- data/MIT-LICENSE +1 -1
- data/README.md +9 -5
- data/app/assets/javascripts/drg_cms/drg_cms.js +69 -32
- data/app/assets/javascripts/drg_cms_application.js +0 -2
- data/app/assets/javascripts/drg_cms_cms.js +2 -3
- data/app/assets/stylesheets/drg_cms/drg_cms.css +89 -26
- data/app/assets/stylesheets/drg_cms/jstree.css +32 -27
- data/app/assets/stylesheets/drg_cms/select-multiple.css +4 -2
- data/app/controllers/cmsedit_controller.rb +9 -111
- data/app/controllers/dc_application_controller.rb +100 -23
- data/app/controllers/dc_common_controller.rb +10 -24
- data/app/controls/browse_models_control.rb +3 -1
- data/app/controls/cmsedit_control.rb +5 -1
- data/app/controls/dc_category_control.rb +61 -0
- data/app/controls/dc_report.rb +1 -1
- data/app/forms/all_options.yml +2 -0
- data/app/forms/cms_menu.yml +3 -2
- data/app/forms/dc_browse_models.yml +24 -2
- data/app/forms/dc_category.yml +17 -8
- data/app/forms/dc_category_as_tree.yml +31 -0
- data/app/forms/dc_steps_template.yml +51 -0
- data/app/forms/help/dc_category_as_tree.en +4 -0
- data/app/forms/help/dc_category_as_tree.sl +5 -0
- data/app/helpers/cms_common_helper.rb +66 -1
- data/app/helpers/cms_edit_helper.rb +230 -121
- data/app/helpers/cms_helper.rb +74 -17
- data/app/helpers/cms_index_helper.rb +40 -37
- data/app/helpers/dc_application_helper.rb +37 -76
- data/app/helpers/dc_category_helper.rb +129 -0
- data/app/models/dc_category.rb +50 -24
- data/app/models/dc_journal.rb +2 -2
- data/app/models/dc_json_ld.rb +18 -41
- data/app/models/drgcms_form_fields/date_picker.rb +10 -12
- data/app/models/drgcms_form_fields/datetime_picker.rb +10 -11
- data/app/models/drgcms_form_fields/drgcms_field.rb +46 -4
- data/app/models/drgcms_form_fields/readonly.rb +1 -1
- data/app/models/drgcms_form_fields/select.rb +2 -2
- data/app/models/drgcms_form_fields/text_autocomplete.rb +2 -2
- data/app/models/drgcms_form_fields/text_with_select.rb +1 -0
- data/app/models/drgcms_form_fields/tree_select.rb +20 -19
- data/app/renderers/dc_common_renderer.rb +20 -3
- data/app/views/cmsedit/_form.html.erb +19 -12
- data/app/views/cmsedit/edit.html.erb +10 -6
- data/app/views/cmsedit/index.html.erb +5 -3
- data/app/views/cmsedit/new.html.erb +9 -5
- data/app/views/dc_common/_help.html.erb +1 -0
- data/app/views/layouts/content.html.erb +1 -1
- data/config/locales/drgcms_en.yml +7 -0
- data/config/locales/drgcms_sl.yml +7 -0
- data/drg_cms.gemspec +3 -3
- data/lib/drg_cms/version.rb +1 -1
- data/lib/tasks/dc_cleanup.rake +20 -42
- metadata +18 -12
- data/History.log +0 -109
@@ -59,7 +59,7 @@ def dc_actions_for_index
|
|
59
59
|
session[:form_processing] = "index:actions: #{key}=#{options}"
|
60
60
|
next if options.nil? # must be
|
61
61
|
|
62
|
-
url = @
|
62
|
+
url = @form_params.clone
|
63
63
|
yaml = options.class == String ? {'type' => options} : options # if single definition simulate type parameter
|
64
64
|
action = yaml['type'].to_s.downcase
|
65
65
|
if action == 'url'
|
@@ -90,8 +90,10 @@ def dc_actions_for_index
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
data = t('drgcms.sort') + select('sort', 'sort', choices, { include_blank: true }, { class: 'dc-sort-select',
|
93
|
-
|
94
|
-
|
93
|
+
'data-table' => @form['table'], 'data-form' => CmsHelper.form_param(params)} )
|
94
|
+
data = mi_icon('sort') + select('sort', 'sort', choices, { include_blank: true }, { class: 'dc-sort-select',
|
95
|
+
'data-table' => @form['table'], 'data-form' => CmsHelper.form_param(params)} )
|
96
|
+
html_right << %(<li title="#{t('drgcms.sort')}"><div class="dc-sort">#{data}</li>)
|
95
97
|
|
96
98
|
# filter
|
97
99
|
when action == 'filter'
|
@@ -102,7 +104,7 @@ def dc_actions_for_index
|
|
102
104
|
html_right << %(
|
103
105
|
<li>
|
104
106
|
<div class="dc-filter" title="#{DcFilter.title4_filter_off(table)}" data-url="#{url.html_safe}">
|
105
|
-
#{mi_icon(url.blank? ? 'search' : '
|
107
|
+
#{mi_icon(url.blank? ? 'search' : 'filter_alt_off') }#{DcFilter.menu_filter(self).html_safe}
|
106
108
|
</div>
|
107
109
|
</li>#{DcFilter.get_filter_field(self)}).html_safe
|
108
110
|
|
@@ -132,7 +134,7 @@ def dc_actions_for_index
|
|
132
134
|
# reorder
|
133
135
|
when action == 'reorder' then
|
134
136
|
caption = t('drgcms.reorder')
|
135
|
-
parms = @
|
137
|
+
parms = @form_params.clone
|
136
138
|
parms['operation'] = v
|
137
139
|
parms['id'] = params[:ids]
|
138
140
|
parms['table'] = @form['table']
|
@@ -257,13 +259,13 @@ def dc_actions_column
|
|
257
259
|
actions = { 'standard' => true } if actions.class == String && actions == 'standard'
|
258
260
|
std_actions = { 2 => 'edit', 5 => 'delete' }
|
259
261
|
if actions['standard']
|
260
|
-
actions.merge!(std_actions)
|
262
|
+
actions.merge!(std_actions)
|
261
263
|
actions.delete('standard')
|
262
264
|
end
|
263
265
|
# check must be first action
|
264
|
-
has_check = actions
|
266
|
+
has_check = actions[0] && actions[0]['type'] == 'check'
|
265
267
|
width = actions.size == 1 ? 22 : 44
|
266
|
-
width = 22 if actions.size > 2
|
268
|
+
width = 22 if actions.size > 2 && !has_check
|
267
269
|
[actions, width, has_check]
|
268
270
|
end
|
269
271
|
|
@@ -290,7 +292,7 @@ def dc_actions_for_result(document)
|
|
290
292
|
main_menu, sub_menu = '', ''
|
291
293
|
actions.sort_by(&:first).each do |num, action|
|
292
294
|
session[:form_processing] = "result_set:actions: #{num}=#{action}"
|
293
|
-
parms = @
|
295
|
+
parms = @form_params.clone
|
294
296
|
# if single definition simulate type parameter
|
295
297
|
yaml = action.class == String ? { 'type' => action } : action
|
296
298
|
|
@@ -362,7 +364,7 @@ def dc_header_for_result
|
|
362
364
|
html = '<div class="dc-result-header">'
|
363
365
|
if @form['result_set']['actions'] && !@form['readonly']
|
364
366
|
ignore, width, has_check = dc_actions_column()
|
365
|
-
check_all = fa_icon('check-
|
367
|
+
check_all = fa_icon('check-box-o', class: 'dc-check-all') if has_check
|
366
368
|
html << %(<div class="dc-result-actions" style="width:#{width}px;">#{check_all}</div>)
|
367
369
|
end
|
368
370
|
# preparation for sort icon
|
@@ -372,22 +374,20 @@ def dc_header_for_result
|
|
372
374
|
end
|
373
375
|
|
374
376
|
if (columns = @form['result_set']['columns'])
|
375
|
-
columns.sort.each do |
|
376
|
-
session[:form_processing] = "result_set:columns: #{
|
377
|
-
next if
|
378
|
-
|
379
|
-
th = %(<div class="th" style="width:#{
|
380
|
-
label =
|
381
|
-
label = (v['name'] ? "helpers.label.#{@form['table']}.#{v['name']}" : '') if label.nil?
|
382
|
-
label = t(label) if label.match(/\./)
|
377
|
+
columns.sort.each do |key, options|
|
378
|
+
session[:form_processing] = "result_set:columns: #{key}=#{options}"
|
379
|
+
next if options['width'].to_s.match(/hidden|none/i)
|
380
|
+
|
381
|
+
th = %(<div class="th" style="width:#{options['width'] || '15%'};text-align:#{options['align'] || 'left'};" data-name="#{options['name']}")
|
382
|
+
label = t_label_for_column(options)
|
383
383
|
# no sorting when embedded documents or custom filter is active
|
384
384
|
sort_ok = !dc_dont?(@form['result_set']['sort'], false)
|
385
385
|
sort_ok = sort_ok || (@form['index'] && @form['index']['sort'])
|
386
|
-
sort_ok = sort_ok && !dc_dont?(
|
386
|
+
sort_ok = sort_ok && !dc_dont?(options['sort'], false)
|
387
387
|
if @tables.size == 1 && sort_ok
|
388
388
|
icon = 'sort_unset md-18'
|
389
|
-
filter_class = form_has_input_field?(
|
390
|
-
if
|
389
|
+
filter_class = form_has_input_field?(options['name']) ? nil : 'no-filter'
|
390
|
+
if options['name'] == sort_field
|
391
391
|
icon = sort_direction == '1' ? 'sort_down md-18' : 'sort_up md-18'
|
392
392
|
else
|
393
393
|
# no icon if filter can not be set
|
@@ -395,7 +395,7 @@ def dc_header_for_result
|
|
395
395
|
end
|
396
396
|
# sort and filter icon
|
397
397
|
icon = mi_icon(icon, class: filter_class) if icon
|
398
|
-
url = url_for(controller: 'cmsedit', action: 'run', control: 'cmsedit.sort', sort:
|
398
|
+
url = url_for(controller: 'cmsedit', action: 'run', control: 'cmsedit.sort', sort: options['name'],
|
399
399
|
t: CmsHelper.table_param(params), f: CmsHelper.form_param(params))
|
400
400
|
th << %(><span data-url="#{url}">#{label}</span>#{icon}</div>)
|
401
401
|
else
|
@@ -472,27 +472,29 @@ end
|
|
472
472
|
def dc_columns_for_result(document)
|
473
473
|
return '' unless @form['result_set']['columns']
|
474
474
|
|
475
|
-
html = ''
|
476
|
-
@form['result_set']['columns'].sort.each do |k,v|
|
475
|
+
html, index = '', 0
|
476
|
+
@form['result_set']['columns'].sort.each do |k, v|
|
477
477
|
session[:form_processing] = "result_set:columns: #{k}=#{v}"
|
478
478
|
next if v['width'].to_s.match(/hidden|none/i)
|
479
479
|
|
480
480
|
# convert shortcut to hash
|
481
481
|
v = {'name' => v} if v.class == String
|
482
482
|
begin
|
483
|
-
|
484
|
-
value = if
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
483
|
+
# as Array (footer)
|
484
|
+
value = if document.class == Array
|
485
|
+
dc_format_value(document[index], v['format']) if document[index]
|
486
|
+
# as Hash (dc_memory)
|
487
|
+
elsif document.class == Hash
|
488
|
+
dc_format_value(document[ v['name'] ], v['format'])
|
489
|
+
# eval
|
490
|
+
elsif v['eval']
|
491
|
+
dc_process_column_eval(v, document)
|
492
|
+
# as field
|
493
|
+
elsif document.respond_to?(v['name'])
|
494
|
+
dc_format_value(document.send( v['name'] ), v['format'])
|
495
|
+
else
|
496
|
+
"??? #{v['name']}"
|
497
|
+
end
|
496
498
|
rescue Exception => e
|
497
499
|
dc_log_exception(e, 'dc_columns_for_result')
|
498
500
|
value = '!!!Error'
|
@@ -507,6 +509,7 @@ def dc_columns_for_result(document)
|
|
507
509
|
style = "style=\"#{width_align}#{style}\" "
|
508
510
|
|
509
511
|
html << "<div class=\"td #{clas}\" #{style}>#{value}</div>"
|
512
|
+
index += 1
|
510
513
|
end
|
511
514
|
html.html_safe
|
512
515
|
end
|
@@ -242,7 +242,7 @@ end
|
|
242
242
|
# and renderers during page rendering.
|
243
243
|
########################################################################
|
244
244
|
def dc_page_bottom
|
245
|
-
%(<style
|
245
|
+
%(<style>#{@css}</style>#{javascript_tag @js}).html_safe
|
246
246
|
end
|
247
247
|
|
248
248
|
############################################################################
|
@@ -258,13 +258,9 @@ end
|
|
258
258
|
############################################################################
|
259
259
|
def dc_table_title(text, result_set = nil)
|
260
260
|
c = %(<div class="dc-title">#{text})
|
261
|
-
|
262
|
-
type = result_set.nil? ? 'form' : 'index'
|
263
|
-
form_name = CmsHelper.form_param(params) || CmsHelper.table_param(params)
|
264
|
-
url = url_for(controller: :dc_common, action: :help, type: type, f: form_name)
|
265
|
-
c << %(<div class="dc-help-icon dc-link-ajax" data-url=#{url}>#{fa_icon('question-circle')}</div>)
|
261
|
+
c << dc_help_button(result_set)
|
266
262
|
|
267
|
-
if result_set
|
263
|
+
if result_set && result_set.respond_to?(:current_page)
|
268
264
|
c << %(<div class="dc-paginate">#{paginate(result_set, :params => {action: 'index', clear: 'no', filter: nil})}</div>)
|
269
265
|
end
|
270
266
|
c << '<div style="clear: both;"></div></div>'
|
@@ -302,11 +298,11 @@ end
|
|
302
298
|
# Returns:
|
303
299
|
# String. HTML code for title.
|
304
300
|
############################################################################
|
305
|
-
def dc_new_title
|
301
|
+
def dc_new_title
|
306
302
|
session[:form_processing] = "form:title:"
|
307
303
|
title = @form['form']['title']
|
308
304
|
# defined as form:title:new
|
309
|
-
if title
|
305
|
+
if title && title['new']
|
310
306
|
t( title['new'], title['new'] )
|
311
307
|
else
|
312
308
|
# in memory structures
|
@@ -318,50 +314,22 @@ def dc_new_title()
|
|
318
314
|
end
|
319
315
|
end
|
320
316
|
|
321
|
-
####################################################################
|
322
|
-
# Formats label and html input code for display on edit form.
|
323
|
-
#
|
324
|
-
# Parameters:
|
325
|
-
# [input_html] String. HTML code for data input field.
|
326
|
-
# [label] String. Input field label.
|
327
|
-
####################################################################
|
328
|
-
def dc_label_for(input_html, label)
|
329
|
-
c =<<eot
|
330
|
-
<tr>
|
331
|
-
<td class="dc-edit-label">#{label}</td>
|
332
|
-
<td class="dc-edit-field">#{input_html}</td>
|
333
|
-
</tr>
|
334
|
-
eot
|
335
|
-
c.html_safe
|
336
|
-
end
|
337
|
-
|
338
317
|
############################################################################
|
339
318
|
# Similar to rails submit_tag, but also takes care of link icon, translation, ...
|
340
319
|
############################################################################
|
341
|
-
def dc_submit_tag(caption, icon, parms, rest={})
|
342
|
-
|
343
|
-
|
344
|
-
icon_image = if icon.match(/\./)
|
345
|
-
image_tag(icon)
|
346
|
-
elsif icon.match('<i')
|
347
|
-
icon
|
348
|
-
else
|
349
|
-
fa_icon(icon)
|
350
|
-
end
|
351
|
-
end
|
352
|
-
html = icon_image || ''
|
353
|
-
#html << submit_tag(t(caption, caption), parms)
|
354
|
-
%Q[<button type="submit" class="dc-submit" name="commit" value="#{t(caption, caption)}">#{icon_image} #{t(caption, caption)}</button>].html_safe
|
320
|
+
def dc_submit_tag(caption, icon, parms, rest = {})
|
321
|
+
icon_image = dc_icon_for_link(icon, nil)
|
322
|
+
%(<button type="submit" class="dc-submit" name="commit" value="#{t(caption, caption)}">#{icon_image} #{t(caption, caption)}</button>).html_safe
|
355
323
|
end
|
356
324
|
|
357
325
|
############################################################################
|
358
326
|
# Returns icon code if icon is specified
|
359
327
|
############################################################################
|
360
|
-
def dc_icon_for_link(icon)
|
328
|
+
def dc_icon_for_link(icon, clas = 'dc-link-img')
|
361
329
|
return '' if icon.blank?
|
362
330
|
|
363
331
|
if icon.match(/\./)
|
364
|
-
_origin.image_tag(icon, class:
|
332
|
+
_origin.image_tag(icon, class: clas)
|
365
333
|
elsif icon.match('<i')
|
366
334
|
icon
|
367
335
|
else
|
@@ -623,7 +591,7 @@ def dc_page_edit_menu(opts = @opts)
|
|
623
591
|
opts[:editparams] ||= {}
|
624
592
|
dc_link_menu_tag(title) do |html|
|
625
593
|
opts[:editparams].merge!( controller: 'cmsedit', action: 'edit', 'icon' => 'edit-o' )
|
626
|
-
opts[:editparams].merge!( :id => page.id, :
|
594
|
+
opts[:editparams].merge!( :id => page.id, :table => _origin.site.page_class.underscore, form_name: opts[:form_name], edit_only: 'body' )
|
627
595
|
html << dc_link_for_edit1( opts[:editparams], t('drgcms.edit_content') )
|
628
596
|
|
629
597
|
opts[:editparams].merge!( edit_only: nil, 'icon' => 'edit-o' )
|
@@ -745,23 +713,6 @@ def dc_choices4_all_collections
|
|
745
713
|
choices.invert.to_a.sort # hash has to be inverted for values to be returned right
|
746
714
|
end
|
747
715
|
|
748
|
-
########################################################################
|
749
|
-
# Merges two forms when current form extends other form. Subroutine of dc_choices4_cmsmenu.
|
750
|
-
# With a little help of https://www.ruby-forum.com/topic/142809
|
751
|
-
########################################################################
|
752
|
-
def forms_merge(hash1, hash2) #:nodoc:
|
753
|
-
target = hash1.dup
|
754
|
-
hash2.keys.each do |key|
|
755
|
-
if hash2[key].is_a? Hash and hash1[key].is_a? Hash
|
756
|
-
target[key] = forms_merge(hash1[key], hash2[key])
|
757
|
-
next
|
758
|
-
end
|
759
|
-
target[key] = hash2[key] == '/' ? nil : hash2[key]
|
760
|
-
end
|
761
|
-
# delete keys with nil value
|
762
|
-
target.delete_if{ |k, v| v.nil? }
|
763
|
-
end
|
764
|
-
|
765
716
|
##########################################################################
|
766
717
|
# Returns choices for creating collection edit select field on CMS top menu.
|
767
718
|
##########################################################################
|
@@ -772,7 +723,7 @@ def dc_choices4_cmsmenu
|
|
772
723
|
next unless File.exist?(filename)
|
773
724
|
menu = YAML.load_file(filename) rescue nil # load menu
|
774
725
|
next if menu.nil? or !menu['menu'] # not menu or error
|
775
|
-
menus = forms_merge(menu['menu'], menus)
|
726
|
+
menus = CmsHelper.forms_merge(menu['menu'], menus) # ignore top level part
|
776
727
|
end
|
777
728
|
|
778
729
|
html = '<ul>'
|
@@ -888,7 +839,7 @@ end
|
|
888
839
|
#
|
889
840
|
# Parameters:
|
890
841
|
# [ctrl] Controller object or object which holds methods to access session object. For example @parent
|
891
|
-
#
|
842
|
+
# when called from renderer.
|
892
843
|
# [policy_id] Document or documents policy_id field value required to view data. Method will automatically
|
893
844
|
# check if parameter send has policy_id field defined and use value of that field.
|
894
845
|
#
|
@@ -903,9 +854,11 @@ end
|
|
903
854
|
# False and message from policy that is blocking view if access is not allowed.
|
904
855
|
############################################################################
|
905
856
|
def dc_user_can_view(ctrl, policy_id)
|
906
|
-
|
857
|
+
@can_view_cache ||= {}
|
858
|
+
policy_id = policy_id.policy_id if policy_id&.respond_to?(:policy_id)
|
907
859
|
# Eventualy object without policy_id will be checked. This is to prevent error
|
908
860
|
policy_id = nil unless policy_id.class == BSON::ObjectId
|
861
|
+
return @can_view_cache[policy_id] if @can_view_cache[policy_id]
|
909
862
|
|
910
863
|
site = ctrl.site
|
911
864
|
policies = if site.inherit_policy.blank?
|
@@ -915,7 +868,7 @@ def dc_user_can_view(ctrl, policy_id)
|
|
915
868
|
end
|
916
869
|
# permission defined by default policy
|
917
870
|
default_policy = Mongoid::QueryCache.cache { policies.find_by(is_default: true) }
|
918
|
-
return false, 'Default access policy not found for the site!' unless default_policy
|
871
|
+
return cache_add(policy_id, false, 'Default access policy not found for the site!') unless default_policy
|
919
872
|
|
920
873
|
permissions = {}
|
921
874
|
default_policy.dc_policy_rules.to_a.each { |v| permissions[v.dc_policy_role_id] = v.permission }
|
@@ -923,28 +876,29 @@ def dc_user_can_view(ctrl, policy_id)
|
|
923
876
|
part_policy = nil
|
924
877
|
if policy_id
|
925
878
|
part_policy = Mongoid::QueryCache.cache { policies.find(policy_id) }
|
926
|
-
return false, 'Access policy not found for part!' unless part_policy
|
879
|
+
return cache_add(policy_id, false, 'Access policy not found for part!') unless part_policy
|
880
|
+
|
927
881
|
part_policy.dc_policy_rules.to_a.each { |v| permissions[v.dc_policy_role_id] = v.permission }
|
928
882
|
end
|
929
883
|
# apply guest role if no roles defined
|
930
884
|
if ctrl.session[:user_roles].nil?
|
931
885
|
role = Mongoid::QueryCache.cache { DcPolicyRole.find_by(system_name: 'guest', active: true) }
|
932
|
-
return false, 'System guest role not defined!' unless role
|
886
|
+
return cache_add(policy_id, false, 'System guest role not defined!') unless role
|
887
|
+
|
933
888
|
ctrl.session[:user_roles] = [role.id]
|
934
889
|
end
|
935
890
|
# Check if user has any role that allows him to view part
|
936
|
-
can_view
|
937
|
-
|
938
|
-
next unless permissions[role] # role not yet defined. Will die in next line.
|
939
|
-
if permissions[role] > 0
|
940
|
-
can_view = true
|
941
|
-
break
|
942
|
-
end
|
891
|
+
can_view = ctrl.session[:user_roles].reduce(false) do |result, role|
|
892
|
+
break true if permissions[role] && permissions[role] > 0
|
943
893
|
end
|
944
|
-
|
945
|
-
|
894
|
+
|
895
|
+
msg = ''
|
896
|
+
unless can_view
|
897
|
+
msg = part_policy ? t(part_policy.message, part_policy.message) : t(default_policy.message, default_policy.message)
|
898
|
+
# message may have variable content
|
899
|
+
msg = _origin.render(inline: msg, layout: nil) if msg.match('<%=')
|
946
900
|
end
|
947
|
-
|
901
|
+
cache_add(policy_id, can_view, msg)
|
948
902
|
end
|
949
903
|
|
950
904
|
####################################################################
|
@@ -1255,5 +1209,12 @@ def dc_img_alt(file_name, text=nil)
|
|
1255
1209
|
name[0,name.index('.')].downcase rescue name
|
1256
1210
|
end
|
1257
1211
|
|
1212
|
+
private
|
1213
|
+
|
1214
|
+
# will cache dc_user_can_view response
|
1215
|
+
def cache_add(id, can_view, msg)
|
1216
|
+
@can_view_cache[id] = [can_view, msg]
|
1217
|
+
end
|
1218
|
+
|
1258
1219
|
|
1259
1220
|
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2012+ Damjan Rems
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
|
25
|
+
####################################################################
|
26
|
+
# Helper for editing categories as tree view.
|
27
|
+
####################################################################
|
28
|
+
module DcCategoryHelper
|
29
|
+
|
30
|
+
####################################################################
|
31
|
+
#
|
32
|
+
####################################################################
|
33
|
+
def categories_as_tree
|
34
|
+
html = '<div id="catagories-as-tree"><ul><li data-id="nil"><span class="mi-o mi-home"></span>'
|
35
|
+
data = DcCategory.where(parent: nil).order_by(order: 1).to_a
|
36
|
+
html_for_category_tree(html, data)
|
37
|
+
(html << '</li></ul></div>' << js_for_category_tree).html_safe
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
####################################################################
|
43
|
+
#
|
44
|
+
####################################################################
|
45
|
+
def html_for_category_tree(html, data)
|
46
|
+
html << '<ul>'
|
47
|
+
data.each do |category|
|
48
|
+
icon = category.active ? 'check_box' : 'check_box_outline_blank'
|
49
|
+
html << %(<li id="#{category.id}" data-parent="#{category.parent}"><span class="mi-o mi-#{icon} mi-18"></span>#{category.name}\n)
|
50
|
+
children = DcCategory.where(parent: category.id).order_by(order: 1).to_a
|
51
|
+
|
52
|
+
html_for_category_tree(html, children) if children.size > 0
|
53
|
+
html << '</li>'
|
54
|
+
end
|
55
|
+
html << '</ul>'
|
56
|
+
end
|
57
|
+
|
58
|
+
####################################################################
|
59
|
+
#
|
60
|
+
####################################################################
|
61
|
+
def js_for_category_tree
|
62
|
+
%(<script>
|
63
|
+
$(function() {
|
64
|
+
$("#catagories-as-tree").jstree( {
|
65
|
+
core: { themes: { icons: false },
|
66
|
+
multiple: false
|
67
|
+
},
|
68
|
+
plugins: ["types", "contextmenu"],
|
69
|
+
contextmenu: {
|
70
|
+
items: function ($node) {
|
71
|
+
return {
|
72
|
+
edit: {
|
73
|
+
label: "<span class='dc-result-submenu'>#{t('drgcms.edit')}</span>",
|
74
|
+
icon: "mi-o mi-edit",
|
75
|
+
action: function (obj) {
|
76
|
+
let id = $('#catagories-as-tree').jstree('get_selected', true)[0].id;
|
77
|
+
let params = "&ids=" + id;
|
78
|
+
location.href = "/cmsedit/" + id + "/edit?t=dc_category&f=dc_category_as_tree" + params;
|
79
|
+
}
|
80
|
+
},
|
81
|
+
|
82
|
+
new_child: {
|
83
|
+
label: "<span class='dc-result-submenu'>#{t('drgcms.new')}</span>",
|
84
|
+
icon: "mi-o mi-plus",
|
85
|
+
action: function (obj) {
|
86
|
+
let id = $('#catagories-as-tree').jstree('get_selected', true)[0].id;
|
87
|
+
let params = "&ids=" + id + "&p_parent=" + id;
|
88
|
+
location.href = "/cmsedit/new?t=dc_category&f=dc_category_as_tree" + params
|
89
|
+
}
|
90
|
+
},
|
91
|
+
|
92
|
+
delete: {
|
93
|
+
label: "<span class='dc-result-submenu'>#{t('drgcms.delete')}</span>",
|
94
|
+
icon: "mi-o mi-delete",
|
95
|
+
action: function (obj) {
|
96
|
+
if (confirmation_is_cancled("#{t('drgcms.confirm_delete')}") === true) return false;
|
97
|
+
|
98
|
+
let id = $('#catagories-as-tree').jstree('get_selected', true)[0].id;
|
99
|
+
let id_return = $('#catagories-as-tree').jstree('get_selected', true)[0].data["parent"];
|
100
|
+
|
101
|
+
$.ajax({
|
102
|
+
url: "/cmsedit/" + id + "?t=dc_category",
|
103
|
+
type: 'DELETE',
|
104
|
+
success: function(data) {
|
105
|
+
let error = data.match("#{I18n.t('drgcms.category_has_subs')}");
|
106
|
+
if (error !== null) {
|
107
|
+
alert(error[0]);
|
108
|
+
params = "?t=dc_category&f=dc_category_as_tree&ids=" + id;
|
109
|
+
location.href = "/cmsedit" + params;
|
110
|
+
return true;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
});
|
114
|
+
|
115
|
+
let params = "?t=dc_category&f=dc_category_as_tree&ids=" + id_return;
|
116
|
+
location.href = "/cmsedit" + params;
|
117
|
+
}
|
118
|
+
},
|
119
|
+
}
|
120
|
+
},
|
121
|
+
},
|
122
|
+
});
|
123
|
+
$("#catagories-as-tree").jstree(true).select_node("#{params[:ids]}");
|
124
|
+
});
|
125
|
+
|
126
|
+
</script>)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
data/app/models/dc_category.rb
CHANGED
@@ -42,32 +42,60 @@
|
|
42
42
|
# is most useful for grouping news, blog entries ...
|
43
43
|
#####################################################################
|
44
44
|
class DcCategory
|
45
|
-
|
46
|
-
|
45
|
+
include Mongoid::Document
|
46
|
+
include Mongoid::Timestamps
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
48
|
+
field :name, type: String
|
49
|
+
field :description, type: String
|
50
|
+
field :ctype, type: Integer, default: 1
|
51
|
+
field :parent, type: BSON::ObjectId
|
52
|
+
field :active, type: Boolean, default: true
|
53
|
+
field :order, type: Integer, default: 0
|
54
|
+
field :created_by, type: BSON::ObjectId
|
55
|
+
field :updated_by, type: BSON::ObjectId
|
56
|
+
field :dc_site_id, type: BSON::ObjectId
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
index name: 1
|
59
|
+
index ctype: 1
|
60
|
+
index site_id: 1
|
61
|
+
|
62
|
+
validates :name, presence: true
|
63
|
+
|
64
|
+
before_destroy :can_destroy?
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
#########################################################################
|
69
|
+
# Can't delete if category document has children documents
|
70
|
+
#########################################################################
|
71
|
+
def can_destroy?
|
72
|
+
if DcCategory.where(parent: id).count > 0
|
73
|
+
errors.add(:base, I18n.t('drgcms.category_has_subs'))
|
74
|
+
throw :abort
|
75
|
+
end
|
76
|
+
end
|
63
77
|
|
64
78
|
#########################################################################
|
65
|
-
# Returns all values
|
79
|
+
# Returns all values for use as parent select field.
|
66
80
|
#########################################################################
|
67
|
-
def self.values_for_parent(site_id=nil) #:nodoc:
|
81
|
+
def self.values_for_parent(site_id = nil) #:nodoc:
|
68
82
|
qry = where(active: true)
|
69
83
|
qry = qry.and(dc_site_id: site_id.id) if site_id
|
70
|
-
|
84
|
+
parents = {} # cache parent names to minimize database usage
|
85
|
+
qry.inject([]) do |r, v|
|
86
|
+
if parents[v.parent].nil?
|
87
|
+
name = ''
|
88
|
+
parent = v.parent
|
89
|
+
until parent.nil?
|
90
|
+
doc = find(parent)
|
91
|
+
name = doc.name + ' / ' + name
|
92
|
+
parent = doc.parent
|
93
|
+
end
|
94
|
+
parents[v.parent] = name
|
95
|
+
end
|
96
|
+
name = v.parent ? parents[v.parent] + v.name : v.name
|
97
|
+
r << [name, v._id]
|
98
|
+
end.sort { |a, b| a.first <=> b.first }
|
71
99
|
end
|
72
100
|
|
73
101
|
#########################################################################
|
@@ -80,8 +108,8 @@ def self.choices4_ctype(site_id=nil)
|
|
80
108
|
DcBigTable.choices4('dc_category_type', site_id)
|
81
109
|
else
|
82
110
|
opts = I18n.t('helpers.label.dc_category.choices4_ctype')
|
83
|
-
# not defined
|
84
|
-
|
111
|
+
return [] if opts.blank? # not defined
|
112
|
+
|
85
113
|
opts.split(',').inject([]) {|result, e| result << e.split(':')}
|
86
114
|
end
|
87
115
|
end
|
@@ -89,13 +117,11 @@ end
|
|
89
117
|
#########################################################################
|
90
118
|
# Returns choices for all categories, prepared for tree_select input field
|
91
119
|
#########################################################################
|
92
|
-
def self.choices4_categories(site_id=nil)
|
120
|
+
def self.choices4_categories(site_id = nil)
|
93
121
|
qry = where(active: true)
|
94
|
-
#
|
95
122
|
ar = [nil]
|
96
123
|
ar << site_id.id if site_id
|
97
124
|
qry = qry.in(dc_site_id: ar)
|
98
|
-
#
|
99
125
|
qry.inject([]) { |result, category| result << [category.name, category.id, category.parent, category.order] }
|
100
126
|
end
|
101
127
|
|
data/app/models/dc_journal.rb
CHANGED