redmine_extensions 0.0.28 → 0.0.29

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