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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +260 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +9 -5
  5. data/app/assets/javascripts/drg_cms/drg_cms.js +69 -32
  6. data/app/assets/javascripts/drg_cms_application.js +0 -2
  7. data/app/assets/javascripts/drg_cms_cms.js +2 -3
  8. data/app/assets/stylesheets/drg_cms/drg_cms.css +89 -26
  9. data/app/assets/stylesheets/drg_cms/jstree.css +32 -27
  10. data/app/assets/stylesheets/drg_cms/select-multiple.css +4 -2
  11. data/app/controllers/cmsedit_controller.rb +9 -111
  12. data/app/controllers/dc_application_controller.rb +100 -23
  13. data/app/controllers/dc_common_controller.rb +10 -24
  14. data/app/controls/browse_models_control.rb +3 -1
  15. data/app/controls/cmsedit_control.rb +5 -1
  16. data/app/controls/dc_category_control.rb +61 -0
  17. data/app/controls/dc_report.rb +1 -1
  18. data/app/forms/all_options.yml +2 -0
  19. data/app/forms/cms_menu.yml +3 -2
  20. data/app/forms/dc_browse_models.yml +24 -2
  21. data/app/forms/dc_category.yml +17 -8
  22. data/app/forms/dc_category_as_tree.yml +31 -0
  23. data/app/forms/dc_steps_template.yml +51 -0
  24. data/app/forms/help/dc_category_as_tree.en +4 -0
  25. data/app/forms/help/dc_category_as_tree.sl +5 -0
  26. data/app/helpers/cms_common_helper.rb +66 -1
  27. data/app/helpers/cms_edit_helper.rb +230 -121
  28. data/app/helpers/cms_helper.rb +74 -17
  29. data/app/helpers/cms_index_helper.rb +40 -37
  30. data/app/helpers/dc_application_helper.rb +37 -76
  31. data/app/helpers/dc_category_helper.rb +129 -0
  32. data/app/models/dc_category.rb +50 -24
  33. data/app/models/dc_journal.rb +2 -2
  34. data/app/models/dc_json_ld.rb +18 -41
  35. data/app/models/drgcms_form_fields/date_picker.rb +10 -12
  36. data/app/models/drgcms_form_fields/datetime_picker.rb +10 -11
  37. data/app/models/drgcms_form_fields/drgcms_field.rb +46 -4
  38. data/app/models/drgcms_form_fields/readonly.rb +1 -1
  39. data/app/models/drgcms_form_fields/select.rb +2 -2
  40. data/app/models/drgcms_form_fields/text_autocomplete.rb +2 -2
  41. data/app/models/drgcms_form_fields/text_with_select.rb +1 -0
  42. data/app/models/drgcms_form_fields/tree_select.rb +20 -19
  43. data/app/renderers/dc_common_renderer.rb +20 -3
  44. data/app/views/cmsedit/_form.html.erb +19 -12
  45. data/app/views/cmsedit/edit.html.erb +10 -6
  46. data/app/views/cmsedit/index.html.erb +5 -3
  47. data/app/views/cmsedit/new.html.erb +9 -5
  48. data/app/views/dc_common/_help.html.erb +1 -0
  49. data/app/views/layouts/content.html.erb +1 -1
  50. data/config/locales/drgcms_en.yml +7 -0
  51. data/config/locales/drgcms_sl.yml +7 -0
  52. data/drg_cms.gemspec +3 -3
  53. data/lib/drg_cms/version.rb +1 -1
  54. data/lib/tasks/dc_cleanup.rake +20 -42
  55. metadata +18 -12
  56. 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 = @parms.clone
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
- 'data-table' => @form['table'], 'data-form' => CmsHelper.form_param(params)} )
94
- html_right << %(<li><div class="dc-sort">#{data}</li>)
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' : 'search_off') }#{DcFilter.menu_filter(self).html_safe}
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 = @parms.clone
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.first[1] == 'check'
266
+ has_check = actions[0] && actions[0]['type'] == 'check'
265
267
  width = actions.size == 1 ? 22 : 44
266
- width = 22 if actions.size > 2 and !has_check
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 = @parms.clone
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-square-o', class: 'dc-check-all') if has_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 |k, v|
376
- session[:form_processing] = "result_set:columns: #{k}=#{v}"
377
- next if v['width'].to_s.match(/hidden|none/i)
378
-
379
- th = %(<div class="th" style="width:#{v['width'] || '15%'};text-align:#{v['align'] || 'left'};" data-name="#{v['name']}")
380
- label = v['caption'] || v['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?(v['sort'], false)
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?(v['name']) ? nil : 'no-filter'
390
- if v['name'] == sort_field
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: v['name'],
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
- # eval
484
- value = if v['eval']
485
- dc_process_column_eval(v, document)
486
- # as field
487
- elsif document.respond_to?(v['name'])
488
- dc_format_value(document.send( v['name'] ), v['format'])
489
- # as hash (dc_memory)
490
- elsif document.class == Hash
491
- dc_format_value(document[ v['name'] ], v['format'])
492
- # error
493
- else
494
- "??? #{v['name']}"
495
- end
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 type="text/css">#{@css}</style>#{javascript_tag @js}).html_safe
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
- # help button
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 and result_set.respond_to?(:current_page)
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 and title['new']
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
- parms['class'] ||= 'dc-link'
343
- if icon
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: 'dc-link-img')
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, :t => _origin.site.page_class.underscore, f: opts[:form_name], edit_only: 'body' )
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) # ignore top level part
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
- # variable when called from renderer.
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
- policy_id = policy_id.policy_id if policy_id and policy_id.respond_to?(:policy_id)
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, msg = false,''
937
- ctrl.session[:user_roles].each do |role|
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
- msg = if !can_view
945
- part_policy ? t(part_policy.message,part_policy.message) : t(default_policy.message,default_policy.message)
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
- return can_view, msg
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
@@ -42,32 +42,60 @@
42
42
  # is most useful for grouping news, blog entries ...
43
43
  #####################################################################
44
44
  class DcCategory
45
- include Mongoid::Document
46
- include Mongoid::Timestamps
45
+ include Mongoid::Document
46
+ include Mongoid::Timestamps
47
47
 
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
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
- validates :name, :presence => true
59
-
60
- index name: 1
61
- index ctype: 1
62
- index site_id: 1
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 vhich can be used as parent select field.
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
- qry.sort(name: 1).inject([]) {|r,v| r << [v.name, v._id]}
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
- return [] if opts.blank?
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
 
@@ -55,6 +55,6 @@ class DcJournal
55
55
  field :time, type: DateTime
56
56
  field :diff, type: String
57
57
 
58
- index( { user_id: 1, time: -1 } )
59
- index( { time: 1 } )
58
+ index user_id: 1
59
+ index doc_id: 1
60
60
  end