drg_cms 0.6.1.9 → 0.7.0.2

Sign up to get free protection for your applications and to get access to all the features.
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