redmine_extensions 0.0.28 → 0.0.29

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 (24) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/redmine_extensions/jquery.entityarray.js +130 -0
  3. data/app/assets/javascripts/redmine_extensions/redmine_extensions.js +287 -0
  4. data/app/helpers/redmine_extensions/application_helper.rb +136 -0
  5. data/app/models/easy_entity_assignment.rb +6 -0
  6. data/app/views/easy_entity_assignments/_assignments_container.html.erb +30 -0
  7. data/app/views/easy_entity_assignments/_query_index.html.erb +11 -0
  8. data/app/views/easy_queries/_index.html.erb +1 -1
  9. data/db/migrate/20160519161300_create_entity_assignments.rb +19 -0
  10. data/lib/generators/redmine_extensions/entity/entity_generator.rb +10 -7
  11. data/lib/generators/redmine_extensions/entity/templates/_form.html.erb.erb +2 -2
  12. data/lib/generators/redmine_extensions/entity/templates/_sidebar.html.erb.erb +2 -2
  13. data/lib/generators/redmine_extensions/entity/templates/controller.rb.erb +3 -0
  14. data/lib/generators/redmine_extensions/entity/templates/en.yml.erb +4 -1
  15. data/lib/generators/redmine_extensions/entity/templates/model.rb.erb +4 -0
  16. data/lib/generators/redmine_extensions/entity/templates/query.rb.erb +3 -3
  17. data/lib/generators/redmine_extensions/entity/templates/routes.rb.erb +1 -0
  18. data/lib/generators/redmine_extensions/entity/templates/show.html.erb.erb +3 -3
  19. data/lib/generators/redmine_extensions/plugin/templates/Gemfile.erb +1 -1
  20. data/lib/redmine_extensions/easy_query_adapter.rb +16 -2
  21. data/lib/redmine_extensions/hooks.rb +1 -0
  22. data/lib/redmine_extensions/redmine_patches/controllers/application_controller_patch.rb +0 -1
  23. data/lib/redmine_extensions/version.rb +1 -1
  24. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2133af14695fcd1b2b6da12d56b498ad5cf1f841
4
- data.tar.gz: 40172f75a841b4f78f6f3888ed482673bae13bd2
3
+ metadata.gz: 1b1aa0e104dd814d76200dc13913de85ee164c63
4
+ data.tar.gz: 69323429240bced2bbe3073800e38da7d8677209
5
5
  SHA512:
6
- metadata.gz: e36b040029f25a9cdfc4b22e15cb9c8f26dd03febe8f153efe2a6421d8d8d5b1bbdf295c55c8ff39145cb78c1ac4273e0dff443201af0723779382aa19fee3f6
7
- data.tar.gz: 3f2bd65fd7239d67859f068eff2b52af75233be4c4a4a0241b47cf49646c3c49baa52289bc94529cf15921690e4c5edf092e833955c8685ee33c5c207cc2c8b8
6
+ metadata.gz: 06f43d7bbd756a988f48003df31d67c416cec480bfe1ea5e97fcf8f8e018bb67b2960618087fc21582de340e40158c222bcc5802b93c592fbce05365b546f269
7
+ data.tar.gz: bb7111418801f304c9a8d163101558ac37d02684b736b6aa0406a85c1ab2e975b1a1df4b7a7abaa83d09bad163253290ad398b1b38ad9852f682fa195a6a6bfe
@@ -0,0 +1,130 @@
1
+ ;(function ($, window, document, undefined) {
2
+ "use strict";
3
+
4
+ var pluginName = "entityArray",
5
+ defaults = {
6
+ propertyName: "value"
7
+ };
8
+
9
+ function Plugin (element, options) {
10
+ this.element = element;
11
+ this.options = $.extend({}, defaults, options);
12
+ this._defaults = defaults;
13
+ this._name = pluginName;
14
+ this.init();
15
+ }
16
+
17
+ Plugin.prototype = {
18
+
19
+ init: function () {
20
+ var self = this;
21
+ this.entities = {};
22
+ this.element.addClass("entity-array");
23
+ this.createEmptyInput();
24
+
25
+ if (this.options.entities) {
26
+ $.each(this.options.entities, function () {
27
+ self.add(this);
28
+ });
29
+ }
30
+ },
31
+
32
+ add: function (entity) {
33
+ var self = this;
34
+
35
+ this.normalizeEntityData(entity);
36
+ if (!entity.id || entity === "" || this.entities[entity.id]) {
37
+ return null;
38
+ }
39
+
40
+ entity.element = $("<span/>")
41
+ .html(entity.name)
42
+ .addClass(entity.className || this.options.className)
43
+ .appendTo(this.element)
44
+ .after(" ");
45
+
46
+ $("<input/>")
47
+ .attr("type", "hidden")
48
+ .attr("name", this.options.inputNames)
49
+ .val(entity.id)
50
+ .appendTo(entity.element);
51
+
52
+ $("<span/>")
53
+ .html('&nbsp;')
54
+ .addClass("icon icon-del")
55
+ .appendTo(entity.element)
56
+ .data("entity-id", entity.id)
57
+ .click(function () {
58
+ self.removeEntity($(this).data("entity-id"));
59
+ });
60
+
61
+ this.entities[entity.id] = entity;
62
+
63
+ this.removeEmptyInput();
64
+
65
+ return entity;
66
+ },
67
+
68
+ removeEntity: function (entityId) {
69
+ var entity = this.entities[entityId];
70
+ entity.element.remove();
71
+
72
+ delete this.entities[entityId];
73
+
74
+ if (Object.keys(this.entities).length === 0) {
75
+ this.createEmptyInput();
76
+ }
77
+
78
+ if (typeof this.options.afterRemove === "function") {
79
+ this.options.afterRemove(entity);
80
+ }
81
+ },
82
+
83
+ clear: function () {
84
+ for( var id in this.entities ) {
85
+ this.removeEntity(id);
86
+ }
87
+ },
88
+
89
+ getValue: function() {
90
+ return Object.keys(this.entities);
91
+ },
92
+
93
+ normalizeEntityData: function (entityData) {
94
+ entityData.id = entityData.id.toString();
95
+ entityData.name = entityData.name.toString();
96
+ },
97
+
98
+ createEmptyInput: function () {
99
+ this.emptyInput = $("<input/>")
100
+ .attr("type", "hidden")
101
+ .attr("name", this.options.inputNames)
102
+ .appendTo(this.element);
103
+ },
104
+
105
+ removeEmptyInput: function () {
106
+ if (this.emptyInput) {
107
+ this.emptyInput.remove();
108
+ this.emptyInput = null;
109
+ }
110
+ }
111
+
112
+ };
113
+
114
+ $.fn[pluginName] = function (options, methodAttrs) {
115
+ var value = null;
116
+ this.each(function () {
117
+ var instance = $.data(this, "plugin_" + pluginName);
118
+ if (!instance) {
119
+ $.data(this, "plugin_" + pluginName, new Plugin($(this), options));
120
+ } else {
121
+ value = instance[options].call(instance, methodAttrs);
122
+ }
123
+ });
124
+ if( value !== null )
125
+ return value;
126
+ else
127
+ return this;
128
+ };
129
+
130
+ })(jQuery, window, document);
@@ -297,3 +297,290 @@ window.showFlashMessage = (function(type, message, delay){
297
297
  window.closeFlashMessage = (function($element){
298
298
  $element.closest('.flash').fadeOut(500, function(){$element.remove();});
299
299
  });
300
+
301
+
302
+ (function($, undefined) {
303
+
304
+ $.widget('easy.easymultiselect', {
305
+ options: {
306
+ source: null,
307
+ selected: null,
308
+ multiple: true, // multiple values can be selected
309
+ preload: true, // load all possible values
310
+ position: {collision: 'flip'},
311
+ autofocus: false,
312
+ inputName: null, // defaults to element prop name
313
+ render_item: function(ul, item) {
314
+ return $("<li>")
315
+ .data("item.autocomplete", item)
316
+ .append(item.label)
317
+ .appendTo(ul);
318
+ },
319
+ activate_on_input_click: true,
320
+ load_immediately: false,
321
+ show_toggle_button: true,
322
+ select_first_value: true,
323
+ autocomplete_options: {}
324
+ },
325
+
326
+ _create: function() {
327
+ this.selectedValues = this.options.selected;
328
+ this._createUI();
329
+ this.expanded = false;
330
+ this.valuesLoaded = false;
331
+ this.afterLoaded = [];
332
+ if ( Array.isArray(this.options.source) ) {
333
+ this.options.preload = true;
334
+ this._initData(this.options.source);
335
+ } else if ( this.options.preload && this.options.load_immediately) {
336
+ this.load();
337
+ }
338
+ },
339
+
340
+ _createUI: function() {
341
+ var that = this;
342
+ this.element.wrap('<span class="easy-autocomplete-tag"></span>');
343
+ this.tag = this.element.parent();
344
+ this.inputName = this.options.inputName || this.element.prop('name');
345
+
346
+ if( this.options.multiple ) { // multiple values
347
+ this.valueElement = $('<span></span>');
348
+ this.tag.after(this.valueElement);
349
+
350
+ if (this.options.show_toggle_button)
351
+ this._createToggleButton();
352
+
353
+ this.valueElement.entityArray({
354
+ inputNames: this.inputName,
355
+ afterRemove: function (entity) {
356
+ that.element.trigger('change');
357
+ }
358
+ });
359
+ } else { //single value
360
+ this.valueElement = $('<input>', {type: 'hidden', name: this.inputName});
361
+ this.element.after(this.valueElement);
362
+ }
363
+
364
+ this._createAutocomplete();
365
+ if( !this.options.multiple ) {
366
+ this.element.css('margin-right', 0);
367
+ }
368
+ },
369
+
370
+ _createToggleButton: function() {
371
+ var that = this;
372
+ this.link_ac_toggle = $('<a>').attr('class', 'icon icon-add clear-link');
373
+ this.link_ac_toggle.click(function(evt) {
374
+ var $elem = $(this);
375
+ evt.preventDefault();
376
+ that.load(function(){
377
+ select = $('<select>').prop('multiple', true).prop('size', 5).prop('name', that.inputName);
378
+ $.each(that.possibleValues, function(i, v) {
379
+ option = $('<option>').prop('value', v.id).text(v.value);
380
+ option.prop('selected', that.getValue().indexOf(v.id) > -1);
381
+ select.append(option);
382
+ });
383
+ $container = $elem.closest('.easy-multiselect-tag-container')
384
+ $container.children().hide();
385
+ $container.append(select);
386
+ that.valueElement = select;
387
+ that.expanded = true;
388
+ });
389
+ });
390
+ this.element.parent().addClass('input-append');
391
+ this.element.after(this.link_ac_toggle);
392
+ },
393
+
394
+ _createAutocomplete: function() {
395
+ var that = this;
396
+
397
+ that.element.autocomplete($.extend({
398
+ source: function(request, response) {
399
+ if( that.options.preload ) {
400
+ that.load(function(){
401
+ var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
402
+ response($.grep(that.possibleValues, function(val, i) {
403
+ return ( !that.options.multiple || !request.term || matcher.test(val.value));
404
+ }));
405
+ }, function(){
406
+ response();
407
+ });
408
+ } else { // asking server everytime
409
+ $.getJSON(that.options.source, {
410
+ term: request.term
411
+ }, function(json) {
412
+ response(that.options.rootElement ? json[that.options.rootElement] : json);
413
+ });
414
+ }
415
+ },
416
+ minLength: 0,
417
+ select: function(event, ui) {
418
+ that.selectValue(ui.item)
419
+ return false;
420
+ },
421
+ change: function(event, ui) {
422
+ if (!ui.item) {
423
+ $(this).val('');
424
+ if( !that.options.multiple ) {
425
+ that.valueElement.val('');
426
+ that.valueElement.change();
427
+ }
428
+ }
429
+ },
430
+ position: this.options.position,
431
+ autoFocus: this.options.autofocus
432
+ }, this.options.autocomplete_options)).data("ui-autocomplete")._renderItem = this.options.render_item;
433
+
434
+ this.element.click(function() {
435
+ $(this).select();
436
+ });
437
+ if( this.options.activate_on_input_click ) {
438
+ this.element.on('click', function() {
439
+ if(!that.options.preload)
440
+ that.element.focus().val('');
441
+ that.element.trigger('keydown');
442
+ that.element.autocomplete("search", that.element.val());
443
+ });
444
+ }
445
+
446
+ $("<button type='button'>&nbsp;</button>")
447
+ .attr("tabIndex", -1)
448
+ .insertAfter(that.element)
449
+ .button({
450
+ icons: {
451
+ primary: "ui-icon-triangle-1-s"
452
+ },
453
+ text: false
454
+ })
455
+ .removeClass("ui-corner-all")
456
+ .addClass("ui-corner-right ui-button-icon")
457
+ .css('font-size', '10px')
458
+ .css('margin-left', -1)
459
+ .click(function() {
460
+ if (that.element.autocomplete("widget").is(":visible")) {
461
+ that.element.autocomplete("close");
462
+ that.element.blur();
463
+ return;
464
+ }
465
+ $(this).blur();
466
+ if(!that.options.preload)
467
+ that.element.focus().val('');
468
+ that.element.trigger('keydown');
469
+ that.element.autocomplete("search", that.element.val());
470
+ });
471
+ },
472
+
473
+ _formatData: function(data) {
474
+ return $.map(data, function(elem, i){
475
+ var id, value;
476
+ if( $.isArray(elem) ) {
477
+ value = elem[0];
478
+ id = elem[1];
479
+ } else {
480
+ id = value = elem;
481
+ }
482
+ return {value: value, id: id};
483
+ });
484
+ },
485
+
486
+ _initData: function(data) {
487
+ this.possibleValues = this._formatData(data)
488
+ this.valuesLoaded = true;
489
+
490
+ this.selectedValues = this.selectedValues ? this.selectedValues : [];
491
+ if( this.selectedValues.length == 0 && this.options.preload && this.options.select_first_value && this.possibleValues.length > 0 ) {
492
+ this.selectedValues.push(this.possibleValues[0]['id'])
493
+ }
494
+
495
+ this.setValue(this.selectedValues);
496
+ },
497
+
498
+ load: function(success, fail) {
499
+ var that = this;
500
+ if( this.valuesLoaded ) {
501
+ if( typeof success === 'function' )
502
+ success();
503
+ return;
504
+ }
505
+
506
+ if( typeof success === 'function' )
507
+ this.afterLoaded.push(success);
508
+
509
+ if( this.loading )
510
+ return;
511
+
512
+ this.loading = true;
513
+ $.ajax(this.options.source, {
514
+ dataType: 'json',
515
+ success: function(data, status, xhr) {
516
+ that._initData(data);
517
+ for (var i = that.afterLoaded.length - 1; i >= 0; i--) {
518
+ that.afterLoaded[i].call();
519
+ }
520
+ },
521
+ error: fail
522
+ }).always(function(){
523
+ that.loading = false;
524
+ });
525
+ },
526
+
527
+ selectValue: function(value) {
528
+ if( this.options.multiple ) {
529
+ this.valueElement.entityArray('add', {
530
+ id: value.id,
531
+ name: value.value
532
+ });
533
+ this.element.trigger('change');
534
+ } else {
535
+ this.element.val(value.value);
536
+ this.valueElement.val(value.id);
537
+ this.valueElement.change();
538
+ }
539
+ },
540
+
541
+ setValue: function(values) {
542
+ var that = this;
543
+ if( typeof values == 'undefined' || !values )
544
+ return false;
545
+
546
+ if( this.options.preload ) {
547
+ this.load(function(){
548
+ if( that.options.multiple ) {
549
+ that.valueElement.entityArray('clear');
550
+ }
551
+ that._setValues(values)
552
+ });
553
+ } else {
554
+ // TODO - where to get real text value?
555
+ this.element.val(values[0]);
556
+ this.valueElement.val(values[0]);
557
+ }
558
+ },
559
+
560
+ _setValues: function(values) {
561
+ var that = this;
562
+ $.each(that.possibleValues, function(i, val) {
563
+ if ( values.indexOf(val.id) > -1 || (values.indexOf(val.id.toString()) > -1)) {
564
+ if(that.options.multiple) {
565
+ that.valueElement.entityArray('add', { id: val.id, name: val.value });
566
+ } else {
567
+ that.element.val(val.value);
568
+ that.valueElement.val(val.id);
569
+ }
570
+ }
571
+ });
572
+ },
573
+
574
+ getValue: function() {
575
+ if( this.options.multiple && !this.expanded ) {
576
+ return this.valueElement.entityArray('getValue'); // entityArray
577
+ } else if ( this.options.multiple ) {
578
+ return this.valueElement.val(); //select multiple=true
579
+ } else {
580
+ return [this.valueElement.val()]; // hidden field
581
+ }
582
+ }
583
+
584
+ });
585
+
586
+ })(jQuery);
@@ -44,6 +44,142 @@ module RedmineExtensions
44
44
  end
45
45
  end
46
46
 
47
+ def render_entity_assignments(entity, target_class, options = {}, &block)
48
+ options ||= {}
49
+ collection_name = options.delete(:collection_name) || target_class.name.pluralize.underscore
50
+
51
+ project = options.delete(:project)
52
+ query_class = options.delete(:query_class)
53
+
54
+ if query_class.nil?
55
+ query_class_name = target_class.name + 'Query'
56
+
57
+ query_class = query_class_name.constantize #if Object.const_defined?(query_class_name)
58
+ end
59
+
60
+ return '' if !query_class || !(query_class < EasyQuery) || !entity.respond_to?(collection_name)
61
+
62
+ query = query_class.new(:name => 'c_query')
63
+ query.project = project
64
+ query.set_entity_scope(entity, collection_name)
65
+ query.column_names = options[:query_column_names] unless options[:query_column_names].blank?
66
+
67
+ entities = query.entities
68
+
69
+ entities_count = entities.size
70
+ options[:entities_count] = entities_count
71
+
72
+ options[:module_name] ||= "entity_#{entity.class.name.underscore}_#{entity.id}_#{collection_name}"
73
+ options[:heading] ||= l("label_#{target_class.name.underscore}_plural", :default => 'Heading')
74
+
75
+ if options[:context_menus_path].nil?
76
+ options[:context_menus_path] = [
77
+ "context_menu_#{collection_name}_path".to_sym,
78
+ "context_menus_#{collection_name}_path".to_sym,
79
+ "#{collection_name}_context_menu_path".to_sym
80
+ ].detect do |m|
81
+ m if respond_to?(m)
82
+ end
83
+ end
84
+
85
+ render(:partial => 'easy_entity_assignments/assignments_container', :locals => {
86
+ :entity => entity,
87
+ :query => query, :target_class => target_class, :project => project,
88
+ :entities => entities, :entities_count => entities_count, :options => options})
89
+ end
90
+
91
+ def entity_css_icon(entity_or_entity_class)
92
+ return '' if entity_or_entity_class.nil?
93
+
94
+ if entity_or_entity_class.is_a?(Class) && entity_or_entity_class.respond_to?(:css_icon)
95
+ entity_or_entity_class.css_icon
96
+ elsif entity_or_entity_class.is_a?(ActiveRecord::Base)
97
+ if entity_or_entity_class.respond_to?(:css_icon)
98
+ entity_or_entity_class.css_icon
99
+ elsif entity_or_entity_class.class.respond_to?(:css_icon)
100
+ entity_or_entity_class.class.css_icon
101
+ else
102
+ "icon icon-#{entity_or_entity_class.class.name.dasherize}"
103
+ end
104
+ else
105
+ "icon icon-#{entity_or_entity_class.class.name.dasherize}"
106
+ end
107
+ end
108
+
109
+ # options:
110
+ # => options[:heading] = text beside of plus button
111
+ # => options[:container_html] = a hash of html attributes
112
+ # => options[:default_button_state] = (true => expanded -), (false => collapsed +)
113
+ # => options[:ajax_call] = make ajax call for saving state (true => ajax call, false => no call, no save)
114
+ # => options[:wrapping_heading_element] = html element outside heading => h3, h4
115
+ def render_toggler(container_uniq_id, user = nil, options={}, &block)
116
+ user ||= User.current
117
+ options[:heading] ||= ''
118
+ options[:heading_links] ||= []
119
+ options[:heading_links] = [options[:heading_links]] if options[:heading_links] && !options[:heading_links].is_a?(Array)
120
+ options[:container_html] ||= {}
121
+ options[:default_button_state] = false #if is_mobile_device?
122
+ options[:default_button_state] = true if options[:default_button_state].nil?
123
+ options[:ajax_call] = true if options[:ajax_call].nil?
124
+
125
+ s = ''
126
+ if !options.key?(:no_heading_button)
127
+ options[:heading] << content_tag(:div, options[:heading_links].join(' ').html_safe, :class => 'module-heading-links') unless options[:heading_links].blank?
128
+ s << render_toggler_header(user, options[:heading].html_safe, container_uniq_id, options)
129
+ end
130
+
131
+ if options[:ajax_call] == false
132
+ expanded = options[:default_button_state]
133
+ else
134
+ expanded = true
135
+ end
136
+
137
+ s << (content_tag(:div, {
138
+ :id => container_uniq_id,
139
+ :style => (expanded ? '' : 'display:none')
140
+ }.merge(options[:container_html]) { |k, o, n| "#{o}; #{n}" }, &block))
141
+ s.html_safe
142
+ end
143
+
144
+ def render_toggler_header(user, content, modul_uniq_id, options={})
145
+ expander_options = options[:expander_options] || {}
146
+ wrapping_heading_element = options[:wrapping_heading_element] || 'h3'
147
+ wrapping_heading_element_classes = (options[:wrapping_heading_element_classes] || '') + ' module-heading'
148
+ wrapping_heading_element_styles = options[:wrapping_heading_element_styles]
149
+ ajax_call = options.delete(:ajax_call) ? 'true' : 'false'
150
+
151
+ html = ''
152
+
153
+ if options[:no_expander]
154
+ html << content_tag(wrapping_heading_element, content, :class => wrapping_heading_element_classes, :style => wrapping_heading_element_styles)
155
+ else
156
+ html << '<div class="module-toggle-button">'
157
+ html << "<div class='group open' >"
158
+ html << content_tag(wrapping_heading_element, content, :class => wrapping_heading_element_classes, :style => wrapping_heading_element_styles, :onclick => "var event = arguments[0] || window.event; if( !$(event.target).hasClass('do_not_toggle') && !$(event.target).parent().hasClass('module-heading-links') ) toggleMyPageModule(this,'#{modul_uniq_id}','#{user.id}', #{ajax_call})")
159
+ html << "<span class='expander #{expander_options[:class]}' onclick=\"toggleMyPageModule($(this),'#{modul_uniq_id}','#{user.id}', #{ajax_call}); return false;\" id=\"expander_#{modul_uniq_id}\">&nbsp;</span>"
160
+ html << '</div></div>'
161
+ end
162
+
163
+ html.html_safe
164
+ end
165
+
166
+ def autocomplete_field_tag(name, jsonpath_or_array, selected_values, options = {})
167
+ options.reverse_merge!({select_first_value: false, show_toggle_button: false, load_immediately: false})
168
+ options[:id] ||= sanitize_to_id(name)
169
+
170
+ selected_values ||= []
171
+
172
+ if jsonpath_or_array.is_a?(Array)
173
+ source = jsonpath_or_array.to_json
174
+ else
175
+ source = "'#{jsonpath_or_array}'"
176
+ end
177
+
178
+ content_tag(:span, :class => 'easy-multiselect-tag-container') do
179
+ text_field_tag('', '', (options[:html_options] || {}).merge(id: options[:id])) +
180
+ javascript_tag("$('##{options[:id]}').easymultiselect({multiple: true, inputName: '#{name}', preload: true, source: #{source}, selected: #{selected_values.to_json}, show_toggle_button: #{options[:show_toggle_button]}, select_first_value: #{options[:select_first_value]}, load_immediately: #{options[:load_immediately]}, autocomplete_options: #{(options[:jquery_auto_complete_options]||{}).to_json} });")
181
+ end
182
+ end
47
183
 
48
184
  end
49
185
  end
@@ -0,0 +1,6 @@
1
+ class EasyEntityAssignment < ActiveRecord::Base
2
+
3
+ belongs_to :entity_from, :polymorphic => true
4
+ belongs_to :entity_to, :polymorphic => true
5
+
6
+ end
@@ -0,0 +1,30 @@
1
+ <%
2
+ display_style = options[:display_style] || (entities_count > 3 ? :list : :tile)
3
+ heading = "#{options[:heading]} (#{entities_count})"
4
+ %>
5
+ <% if entities_count > 0 %>
6
+ <div id="<%= options[:module_name] %>_container" class="entity_references_container easy-dropper-target easy-drop-issue <%= display_style.to_s %>" data-drop-action="<%= entity.class.name.underscore %>" data-entity-id="<%= entity.id %>" data-issue-show="1">
7
+ <%= call_hook(:view_easy_entity_assignemnt_top, :entity => entity, :query => query, :entities => entities, :options => options, :target_class => target_class) %>
8
+ <div class="box">
9
+ <%# entity_cards_params = {:module_name => options[:module_name], :project_id => project, :source_entity_type => entity.class.name, :source_entity_id => entity, :referenced_entity_type => referenced_entity_type, :referenced_collection_name => options[:referenced_collection_name]}.merge!(query.to_params) %>
10
+ <% entity_cards_params = {} %>
11
+ <%= render_toggler(options[:module_name], User.current, {:heading => heading + ':', :wrapping_heading_element_classes => entity_css_icon(target_class),
12
+ :container_html => {:class => 'module-inside'}, :default_button_state => false, :heading_links => [
13
+ link_to('', {}, :class => 'icon-slab', :remote => true, :data => {:display => 'slab'}, :title => l(:title_easy_card_display_changer_tile)),
14
+ link_to('', {}, :class => 'icon-bullet-list', :remote => true, :data => {:display => 'list'}, :title => l(:title_easy_card_display_changer_list))
15
+ ]}) do %>
16
+
17
+
18
+ <%= render(:partial => 'easy_entity_assignments/query_index', :locals => {
19
+ :entity => entity,
20
+ :query => query, :target_class => target_class,
21
+ :project => project, :options => options, :entities => entities, :display_style => display_style}) %>
22
+
23
+
24
+ <% if options[:context_menus_path] && respond_to?(options[:context_menus_path]) %>
25
+ <%# context_menu(send(options[:context_menus_path], {:project_id => project, :back_url => url_for(params)}), "##{options[:module_name]}_container") -%>
26
+ <% end %>
27
+ <% end %>
28
+ </div>
29
+ </div>
30
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <% if display_style == :list %>
2
+ <%= render :partial => 'easy_queries/easy_query_entities_list', :locals => {:query => query, :entities => referenced_entities, :options => {:hascontextmenu => true, :disable_sort => true} } -%>
3
+ <% elsif display_style == :tile %>
4
+ <div class="easy-entity-cards-container">
5
+ <div class="splitcontent">
6
+ <% entities.each do |referenced_entity| %>
7
+ <%# render_easy_entity_card(referenced_entity, entity, options.merge(:referenced_entities => referenced_entities)) %>
8
+ <% end %>
9
+ </div>
10
+ </div>
11
+ <% end %>
@@ -7,4 +7,4 @@
7
7
  <%= render 'easy_queries/settings', query: query %>
8
8
  <%= render 'easy_queries/entities', query: query, entities: entities %>
9
9
 
10
- <%= context_menu issues_context_menu_path %>
10
+ <%# context_menu issues_context_menu_path %>
@@ -0,0 +1,19 @@
1
+ class CreateEntityAssignments < ActiveRecord::Migration
2
+ def self.up
3
+ unless table_exists?(:easy_entity_assignments)
4
+ create_table :easy_entity_assignments do |t|
5
+ t.references :entity_from, :polymorphic => true
6
+ t.references :entity_to, :polymorphic => true
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :easy_entity_assignments, [:entity_from_type, :entity_from_id, :entity_to_type, :entity_to_id], :name => 'entity_assignment_idx', :unique => true
11
+ add_index :easy_entity_assignments, :entity_from_id, :name => 'entity_assignment_idx_from'
12
+ add_index :easy_entity_assignments, :entity_to_id, :name => 'entity_assignment_idx_to'
13
+ end
14
+ end
15
+
16
+ def self.down
17
+ drop_table :easy_entity_assignments
18
+ end
19
+ end
@@ -48,7 +48,11 @@ module RedmineExtensions
48
48
 
49
49
  if File.exists?("#{plugin_path}/config/locales/en.yml")
50
50
  append_to_file "#{plugin_path}/config/locales/en.yml" do
51
- "\n easy_query:" +
51
+ "\n activerecord:" +
52
+ "\n easy_query:" +
53
+ "\n attributes:" +
54
+ "\n #{model_name_underscored}:" +
55
+ db_columns.collect { |column_name, column_options| "\n #{column_options[:lang_key]}: #{column_name.humanize}" }.join +
52
56
  "\n name:" +
53
57
  "\n #{model_name_underscored}_query: #{model_name_pluralize_underscored.titleize}" +
54
58
  "\n heading_#{model_name_underscored}_new: New #{model_name_underscored.titleize}" +
@@ -60,9 +64,6 @@ module RedmineExtensions
60
64
  "\n permission_manage_#{model_name_pluralize_underscored}: Manage #{model_name_pluralize_underscored.titleize}" +
61
65
  "\n title_#{model_name_underscored}_new: Click to create new #{model_name_underscored.titleize}"
62
66
  end
63
- append_to_file "#{plugin_path}/config/locales/en.yml" do
64
- db_columns.collect { |column_name, column_options| "\n #{column_options[:lang_key]}: #{column_name.humanize}" }.join
65
- end
66
67
  else
67
68
  template 'en.yml.erb', "#{plugin_path}/config/locales/en.yml"
68
69
  end
@@ -96,6 +97,7 @@ module RedmineExtensions
96
97
  append_to_file "#{plugin_path}/config/routes.rb" do
97
98
  "\nresources :#{model_name_pluralize_underscored} do" +
98
99
  "\n collection do " +
100
+ "\n get 'autocomplete'" +
99
101
  "\n get 'bulk_edit'" +
100
102
  "\n post 'bulk_update'" +
101
103
  "\n get 'context_menu'" +
@@ -111,8 +113,8 @@ module RedmineExtensions
111
113
  s << "\n require '#{plugin_name_underscored}/#{model_name_underscored}_hooks'\n"
112
114
  s << "\n Redmine::AccessControl.map do |map|"
113
115
  s << "\n map.project_module :#{model_name_pluralize_underscored} do |pmap|"
114
- s << "\n pmap.permission :view_#{model_name_pluralize_underscored}, { #{model_name_pluralize_underscored}: [:index, :show] }, read: true"
115
- s << "\n pmap.permission :manage_#{model_name_pluralize_underscored}, { #{model_name_pluralize_underscored}: [:new, :create, :edit, :update, :destroy, :bulk_edit, :bulk_update, :context_menu] }"
116
+ s << "\n pmap.permission :view_#{model_name_pluralize_underscored}, { #{model_name_pluralize_underscored}: [:index, :show, :autocomplete, :context_menu] }, read: true"
117
+ s << "\n pmap.permission :manage_#{model_name_pluralize_underscored}, { #{model_name_pluralize_underscored}: [:new, :create, :edit, :update, :destroy, :bulk_edit, :bulk_update] }"
116
118
  s << "\n end "
117
119
  s << "\n end\n"
118
120
  s << "\n Redmine::MenuManager.map :top_menu do |menu|"
@@ -201,7 +203,8 @@ module RedmineExtensions
201
203
 
202
204
  attributes.each do |attr|
203
205
  attr_name, attr_type, attr_idx = attr.split(':')
204
- lang_key = "field_#{model_name_underscored}_#{attr_name.to_s.sub(/_id$/, '').sub(/^.+\./, '')}"
206
+ #lang_key = "field_#{model_name_underscored}_#{attr_name.to_s.sub(/_id$/, '').sub(/^.+\./, '')}"
207
+ lang_key = "#{attr_name.to_s.sub(/_id$/, '').sub(/^.+\./, '')}"
205
208
 
206
209
  @db_columns[attr_name] = {type: attr_type || 'string', idx: attr_idx, null: true, safe: true, query_type: attr_type || 'string', lang_key: lang_key}
207
210
  end
@@ -2,7 +2,7 @@
2
2
  <%- if project? -%>
3
3
  <%% if <%= model_name_underscored %>.safe_attribute?('project_id') && !@project %>
4
4
  <p>
5
- <%%= f.label :project_id, <%= model_name %>.human_attribute_name(:project_id) %>
5
+ <%%= f.label :project_id, ::<%= model_name %>.human_attribute_name(:project_id) %>
6
6
  <%%= f.select :project_id, Project.allowed_to(:manage_<%= model_name_pluralize_underscored %>).collect{|x| [x.name, x.id]}, include_blank: true %>
7
7
  </p>
8
8
  <%% end %>
@@ -10,7 +10,7 @@
10
10
  <%- safe_columns.each do |column_name, column_options| -%>
11
11
  <%% if <%= model_name_underscored %>.safe_attribute?('<%= column_name %>') %>
12
12
  <p>
13
- <%%= f.label :<%= column_name %>, <%= model_name %>.human_attribute_name(:<%= column_name %>) %>
13
+ <%%= f.label :<%= column_name %>, ::<%= model_name %>.human_attribute_name(:<%= column_name %>) %>
14
14
  <%- if column_options[:query_type] == 'string' || column_options[:query_type] == 'integer' -%>
15
15
  <%%= f.text_field :<%= column_name %> %>
16
16
  <%- elsif column_options[:query_type] == 'list' || column_options[:query_type] == 'list_optional' -%>
@@ -3,7 +3,7 @@
3
3
  <li><%%= link_to l(:button_edit), edit_polymorphic_path([@project, @<%= model_name_underscored %>]), title: l(:button_edit), class: 'icon icon-edit' %></li>
4
4
  <%% end %>
5
5
  <%% if @<%= model_name_underscored %>.nil? %>
6
- <li><%%= link_to l(:button_<%= model_name_underscored %>_new), new_polymorphic_path([@project, :<%= model_name_underscored %>]), title: l(:title_<%= model_name_underscored %>_new), class: 'icon icon-add' %></li>
6
+ <li><%%= link_to l(:button_<%= model_name_underscored %>_new), new_polymorphic_path([@project, :<%= model_name_underscored %>]), title: l(:title_<%= model_name_underscored %>_new), class: 'icon icon-add button button-positive' %></li>
7
7
  <%% end %>
8
- <li><%%= link_to l(:label_<%= model_name_pluralize_underscored %>), polymorphic_path([@project, <%= model_name %>], set_filter: '1'), title: l(:label_<%= model_name_pluralize_underscored %>), class: 'icon icon-folder' %></li>
8
+ <li><%%= link_to l(:label_<%= model_name_pluralize_underscored %>), polymorphic_path([@project, :<%= model_name_pluralize_underscored %>], set_filter: '1'), title: l(:label_<%= model_name_pluralize_underscored %>), class: 'icon icon-folder button' %></li>
9
9
  </ul>
@@ -128,6 +128,9 @@ class <%= controller_class %>Controller < ApplicationController
128
128
  render :layout => false
129
129
  end
130
130
 
131
+ def autocomplete
132
+ end
133
+
131
134
  private
132
135
 
133
136
  def find_<%= model_name_underscored %>
@@ -1,4 +1,8 @@
1
1
  en:
2
+ activerecord:
3
+ attributes:
4
+ <%= model_name_underscored %>:
5
+ <%= db_columns.collect{|column_name, column_options| "\n #{column_options[:lang_key]}: #{column_name.humanize}" }.join %>
2
6
  easy_query:
3
7
  name:
4
8
  <%= model_name_underscored %>_query: <%= model_name_pluralize_underscored.titleize %>
@@ -10,4 +14,3 @@ en:
10
14
  permission_view_<%= model_name_pluralize_underscored %>: View <%= model_name_pluralize_underscored.titleize %>
11
15
  permission_manage_<%= model_name_pluralize_underscored %>: Manage <%= model_name_pluralize_underscored.titleize %>
12
16
  title_<%= model_name_underscored %>_new: Click to create new <%= model_name_underscored.titleize %>
13
- <%= db_columns.collect{|column_name, column_options| "\n #{column_options[:lang_key]}: #{column_name.humanize}" }.join %>
@@ -53,6 +53,10 @@ class <%= model_name %> < ActiveRecord::Base
53
53
  after_update :send_update_notification
54
54
  <%- end -%>
55
55
 
56
+ def self.human_attribute_name(attribute, *args)
57
+ l(attribute.to_s.gsub(/\_id$/, ''), :scope => [:activerecord, :attributes, :<%= model_name_underscored %>])
58
+ end
59
+
56
60
  def self.visible_condition(user, options={})
57
61
  <%- if project? -%>
58
62
  Project.allowed_to_condition(user, :<%= view_permission %>, options)
@@ -6,10 +6,10 @@ class <%= model_name %>Query < EasyQuery
6
6
  <%- db_columns.each do |column_name, column_options| -%>
7
7
  <%- if project? && column_name == 'project_id' -%>
8
8
  if project.nil?
9
- add_available_filter '<%= column_name %>', name: <%= model_name %>.human_attribute_name(:<%= column_name %>), type: :<%= column_options[:query_type] %>, values: all_projects_values
9
+ add_available_filter '<%= column_name %>', name: ::<%= model_name %>.human_attribute_name(:<%= column_name %>), type: :<%= column_options[:query_type] %>, values: all_projects_values
10
10
  end
11
11
  <%- else -%>
12
- add_available_filter '<%= column_name %>', name: <%= model_name %>.human_attribute_name(:<%= column_name %>), type: :<%= column_options[:query_type] -%>
12
+ add_available_filter '<%= column_name %>', name: ::<%= model_name %>.human_attribute_name(:<%= column_name %>), type: :<%= column_options[:query_type] -%>
13
13
  <%- end -%>
14
14
  <%- end -%>
15
15
  <%- if acts_as_customizable? -%>
@@ -21,7 +21,7 @@ class <%= model_name %>Query < EasyQuery
21
21
  return @available_columns if @available_columns
22
22
 
23
23
  <%- db_columns.each do |column_name, column_options| -%>
24
- add_available_column '<%= column_options[:query_column_name] || column_name %>', title: <%= model_name %>.human_attribute_name(:<%= column_name %>)
24
+ add_available_column '<%= column_options[:query_column_name] || column_name %>', caption: ::<%= model_name %>.human_attribute_name(:<%= column_name %>)
25
25
  <%- end -%>
26
26
  <%- if acts_as_customizable? -%>
27
27
  @available_columns += <%= model_name %>CustomField.visible.collect {|cf| QueryCustomFieldColumn.new(cf) }
@@ -5,6 +5,7 @@ end
5
5
  <% end %>
6
6
  resources :<%= model_name_pluralize_underscored %> do
7
7
  collection do
8
+ get 'autocomplete'
8
9
  get 'bulk_edit'
9
10
  post 'bulk_update'
10
11
  get 'context_menu'
@@ -5,9 +5,9 @@
5
5
  <%- safe_columns.each_with_index do |column, idx| -%>
6
6
  <%- next if description_column? && column[0] == description_column -%>
7
7
  <%- if idx % 2 == 0 -%>
8
- <%% rows.left <%= model_name %>.human_attribute_name(:<%= column[0] %>), format_object(@<%= model_name_underscored %>.<%= column[0] %>) %>
8
+ <%% rows.left ::<%= model_name %>.human_attribute_name(:<%= column[0] %>), format_object(@<%= model_name_underscored %>.<%= column[0] %>) %>
9
9
  <%- else -%>
10
- <%% rows.right <%= model_name %>.human_attribute_name(:<%= column[0] %>), format_object(@<%= model_name_underscored %>.<%= column[0] %>) %>
10
+ <%% rows.right ::<%= model_name %>.human_attribute_name(:<%= column[0] %>), format_object(@<%= model_name_underscored %>.<%= column[0] %>) %>
11
11
  <%- end -%>
12
12
  <%- end -%>
13
13
  <%% end %>
@@ -18,7 +18,7 @@
18
18
  <%% if !@<%= model_name_underscored %>.<%= description_column %>.blank? %>
19
19
  <hr />
20
20
  <div class="description">
21
- <p><strong><%%= <%= model_name %>.human_attribute_name(:<%= description_column %>) %></strong></p>
21
+ <p><strong><%%= ::<%= model_name %>.human_attribute_name(:<%= description_column %>) %></strong></p>
22
22
  <div class="wiki">
23
23
  <%%= textilizable @<%= model_name_underscored %>, :<%= description_column %>, attachments: @<%= model_name_underscored %>.attachments %>
24
24
  </div>
@@ -1 +1 @@
1
- gem 'redmine_extensions', '~> 0.0.21'
1
+ gem 'redmine_extensions' unless Dir.exist?(File.expand_path('../../easyproject', __FILE__))
@@ -63,8 +63,12 @@ class EasyQueryAdapter < Query
63
63
  @formatter = formatter_klass.new(object)
64
64
  end
65
65
 
66
+ def entity
67
+ self.class.queried_class
68
+ end
69
+
66
70
  def entity_scope
67
- if @entity_scope.present?
71
+ if !@entity_scope.nil?
68
72
  @entity_scope
69
73
  elsif entity.respond_to?(:visible)
70
74
  entity.visible
@@ -73,6 +77,16 @@ class EasyQueryAdapter < Query
73
77
  end
74
78
  end
75
79
 
80
+ def set_entity_scope(entity, reference_collection = nil)
81
+ return nil if entity.nil? || reference_collection.nil?
82
+
83
+ @entity_scope = entity.send(reference_collection.to_sym)
84
+
85
+ self.filters = {}
86
+
87
+ @entity_scope
88
+ end
89
+
76
90
  def create_entity_scope(options={})
77
91
  scope = entity_scope.where(statement)
78
92
 
@@ -111,7 +125,7 @@ class EasyQueryAdapter < Query
111
125
  end
112
126
 
113
127
  def entities(options={})
114
- create_entity_scope(options).order(options[:order])
128
+ create_entity_scope(options).order(options[:order]).to_a
115
129
  end
116
130
 
117
131
  def add_available_column(name, options={})
@@ -2,6 +2,7 @@ module RedmineExtensions
2
2
  class Hooks < Redmine::Hook::ViewListener
3
3
 
4
4
  def view_layouts_base_html_head(context={})
5
+ javascript_include_tag('redmine_extensions/jquery.entityarray') +
5
6
  javascript_include_tag('redmine_extensions/redmine_extensions')
6
7
  end
7
8
 
@@ -52,5 +52,4 @@ module RedmineExtensions
52
52
 
53
53
  end
54
54
  end
55
-
56
55
  RedmineExtensions::PatchManager.register_controller_patch 'ApplicationController', 'RedmineExtensions::ApplicationControllerPatch'
@@ -1,3 +1,3 @@
1
1
  module RedmineExtensions
2
- VERSION = '0.0.28'
2
+ VERSION = '0.0.29'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redmine_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.28
4
+ version: 0.0.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Easy Software Ltd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-10 00:00:00.000000000 Z
11
+ date: 2016-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -105,14 +105,18 @@ files:
105
105
  - LICENSE
106
106
  - Rakefile
107
107
  - app/assets/javascripts/redmine_extensions/application.js
108
+ - app/assets/javascripts/redmine_extensions/jquery.entityarray.js
108
109
  - app/assets/javascripts/redmine_extensions/redmine_extensions.js
109
110
  - app/assets/stylesheets/redmine_extensions/application.css
110
111
  - app/controllers/easy_settings_controller.rb
111
112
  - app/helpers/redmine_extensions/application_helper.rb
112
113
  - app/helpers/redmine_extensions/rendering_helper.rb
114
+ - app/models/easy_entity_assignment.rb
113
115
  - app/models/easy_setting.rb
114
116
  - app/presenters/redmine_extensions/base_presenter.rb
115
117
  - app/presenters/redmine_extensions/easy_setting_presenter.rb
118
+ - app/views/easy_entity_assignments/_assignments_container.html.erb
119
+ - app/views/easy_entity_assignments/_query_index.html.erb
116
120
  - app/views/easy_queries/_entities.html.erb
117
121
  - app/views/easy_queries/_index.html.erb
118
122
  - app/views/easy_queries/_settings.html.erb
@@ -121,6 +125,7 @@ files:
121
125
  - config/locales/en.yml
122
126
  - config/routes.rb
123
127
  - db/migrate/20150705172511_create_easy_settings.rb
128
+ - db/migrate/20160519161300_create_entity_assignments.rb
124
129
  - lib/generators/redmine_extensions/entity/USAGE
125
130
  - lib/generators/redmine_extensions/entity/entity_generator.rb
126
131
  - lib/generators/redmine_extensions/entity/templates/_form.html.erb.erb