netzke-basepack 0.6.3 → 0.6.4

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 (98) hide show
  1. data/CHANGELOG.rdoc +17 -0
  2. data/README.rdoc +1 -1
  3. data/Rakefile +3 -0
  4. data/TODO.rdoc +1 -4
  5. data/features/form_panel.feature +2 -1
  6. data/features/grid_panel.feature +134 -5
  7. data/features/nested_attributes.feature +4 -1
  8. data/features/paging_form_panel.feature +41 -0
  9. data/features/search_in_grid.feature +20 -7
  10. data/features/step_definitions/form_panel_steps.rb +35 -0
  11. data/features/step_definitions/generic_steps.rb +20 -1
  12. data/features/step_definitions/grid_panel_steps.rb +63 -0
  13. data/features/support/env.rb +4 -0
  14. data/features/validations_in_grid.feature +13 -0
  15. data/features/virtual_attributes.feature +5 -9
  16. data/javascripts/basepack.js +21 -650
  17. data/javascripts/datetimefield.js +24 -0
  18. data/lib/generators/netzke/basepack_generator.rb +10 -0
  19. data/{generators/netzke_basepack/templates/public_assets → lib/generators/netzke/templates/assets}/ts-checkbox.gif +0 -0
  20. data/{generators/netzke_basepack → lib/generators/netzke}/templates/create_netzke_field_lists.rb +0 -0
  21. data/lib/netzke-basepack.rb +3 -41
  22. data/lib/netzke/active_record/attributes.rb +17 -21
  23. data/lib/netzke/basepack.rb +6 -1
  24. data/lib/netzke/basepack/data_accessor.rb +166 -0
  25. data/lib/netzke/basepack/form_panel.rb +69 -20
  26. data/lib/netzke/basepack/form_panel/fields.rb +15 -17
  27. data/lib/netzke/basepack/form_panel/javascripts/comma_list_cbg.js +7 -0
  28. data/lib/netzke/basepack/form_panel/javascripts/display_mode.js +6 -0
  29. data/lib/netzke/basepack/form_panel/javascripts/{main.js → form_panel.js} +42 -8
  30. data/lib/netzke/basepack/form_panel/javascripts/n_radio_group.js +24 -7
  31. data/lib/netzke/basepack/form_panel/javascripts/readonly_mode.js +52 -0
  32. data/lib/netzke/basepack/form_panel/services.rb +28 -2
  33. data/lib/netzke/basepack/form_panel/stylesheets/readonly_mode.css +14 -0
  34. data/lib/netzke/basepack/grid_panel.rb +151 -181
  35. data/lib/netzke/basepack/grid_panel/columns.rb +122 -84
  36. data/lib/netzke/basepack/grid_panel/javascripts/advanced_search.js +22 -27
  37. data/lib/netzke/basepack/grid_panel/javascripts/{main.js → grid_panel.js} +182 -108
  38. data/lib/netzke/basepack/grid_panel/record_form_window.rb +7 -2
  39. data/lib/netzke/basepack/grid_panel/services.rb +58 -39
  40. data/lib/netzke/basepack/paging_form_panel.rb +86 -25
  41. data/lib/netzke/basepack/query_builder.rb +105 -0
  42. data/lib/netzke/basepack/query_builder/javascripts/query_builder.js +140 -0
  43. data/lib/netzke/basepack/search_panel.rb +61 -44
  44. data/lib/netzke/basepack/search_panel/javascripts/condition_field.js +153 -0
  45. data/lib/netzke/basepack/search_panel/javascripts/search_panel.js +64 -0
  46. data/lib/netzke/basepack/search_window.rb +64 -0
  47. data/lib/netzke/basepack/simple_app.rb +1 -1
  48. data/lib/netzke/basepack/simple_app/javascripts/{main.js → simple_app.js} +0 -0
  49. data/lib/netzke/basepack/tab_panel.rb +1 -2
  50. data/lib/netzke/basepack/tab_panel/javascripts/{main.js → tab_panel.js} +0 -0
  51. data/lib/netzke/basepack/version.rb +1 -1
  52. data/locales/en.yml +71 -4
  53. data/netzke-basepack.gemspec +47 -22
  54. data/spec/active_record/attributes_spec.rb +6 -6
  55. data/spec/components/form_panel_spec.rb +2 -13
  56. data/stylesheets/datetimefield.css +54 -0
  57. data/test/rails_app/Gemfile +3 -3
  58. data/test/rails_app/Gemfile.lock +67 -57
  59. data/test/rails_app/README +1 -256
  60. data/test/rails_app/app/components/book_form.rb +1 -3
  61. data/test/rails_app/app/components/book_form_with_custom_fields.rb +20 -0
  62. data/test/rails_app/app/components/book_form_with_nested_attributes.rb +18 -0
  63. data/test/rails_app/app/components/book_grid.rb +3 -1
  64. data/test/rails_app/app/components/book_grid_loader.rb +24 -0
  65. data/test/rails_app/app/components/book_grid_with_custom_columns.rb +28 -0
  66. data/test/rails_app/app/components/book_grid_with_default_values.rb +1 -1
  67. data/test/rails_app/app/components/book_grid_with_virtual_attributes.rb +0 -1
  68. data/test/rails_app/app/components/book_paging_form_panel.rb +3 -2
  69. data/test/rails_app/app/components/book_presentation.rb +3 -3
  70. data/test/rails_app/app/components/book_query_builder.rb +8 -0
  71. data/test/rails_app/app/components/book_search_panel.rb +5 -0
  72. data/test/rails_app/app/components/book_search_panel/javascripts/i18n_de.js +6 -0
  73. data/test/rails_app/app/components/double_book_grid.rb +18 -0
  74. data/test/rails_app/app/components/form_without_model.rb +1 -1
  75. data/test/rails_app/app/components/paging_form_with_search.rb +39 -0
  76. data/test/rails_app/app/components/user_grid.rb +1 -1
  77. data/test/rails_app/app/models/author.rb +1 -0
  78. data/test/rails_app/config/application.rb +6 -1
  79. data/test/rails_app/config/database.yml +7 -5
  80. data/test/rails_app/config/environments/test.rb +1 -1
  81. data/test/rails_app/config/locales/de.yml +35 -0
  82. data/test/rails_app/config/locales/es.yml +84 -8
  83. data/test/rails_app/db/migrate/20101026190021_create_books.rb +2 -2
  84. data/test/rails_app/db/migrate/20110213213050_create_netzke_component_states.rb +20 -0
  85. data/test/rails_app/db/schema.rb +2 -18
  86. data/test/rails_app/public/netzke/basepack/ts-checkbox.gif +0 -0
  87. data/test/unit/active_record_basepack_test.rb +1 -1
  88. metadata +46 -45
  89. data/generators/netzke_basepack/netzke_basepack_generator.rb +0 -13
  90. data/lib/netzke/basepack/grid_panel/search_window.rb +0 -56
  91. data/lib/netzke/data_accessor.rb +0 -113
  92. data/lib/netzke/ext.rb +0 -7
  93. data/lib/netzke/fields_configurator.rb +0 -170
  94. data/lib/netzke/json_array_editor.rb +0 -67
  95. data/lib/netzke/masquerade_selector.rb +0 -53
  96. data/test/rails_app/app/components/some_search_panel.rb +0 -34
  97. data/test/rails_app/db/development_structure.sql +0 -93
  98. data/test/rails_app/db/migrate/20100905214933_create_netzke_preferences.rb +0 -16
@@ -0,0 +1,140 @@
1
+ {
2
+ initComponent: function() {
3
+ Netzke.classes.Basepack.QueryBuilder.superclass.initComponent.call(this);
4
+ this.add({title: "+"});
5
+
6
+ this.on('beforetabchange', function(c, newTab, curentTab){
7
+ if (newTab.title === '+') {
8
+ this.addTab(true);
9
+ return false;
10
+ } else {
11
+ if (this.maxTabHeight) newTab.setHeight(this.maxTabHeight);
12
+ }
13
+ }, this);
14
+
15
+ this.addEvents('conditionsupdate');
16
+
17
+ },
18
+
19
+ buildFormFromQuery: function(query) {
20
+ this.onClearAll();
21
+
22
+ if (query.length !== 0) {
23
+ Ext.each(query, function(f, i){
24
+ if (this.items.getCount() < i + 2) { this.addTab(); }
25
+ this.items.get(i).buildFormFromQuery(query[i]);
26
+ }, this);
27
+ }
28
+
29
+ this.doLayout();
30
+ },
31
+
32
+ addTab: function(activate){
33
+ var newTabConfig = Ext.apply({}, this.components.searchPanel);
34
+ newTabConfig.id = Ext.id(); // We need a unique ID every time
35
+ newTabConfig.title = "OR";
36
+ newTabConfig.closable = true;
37
+ var newTab = Ext.create(newTabConfig);
38
+
39
+ this.insert(this.items.getCount() - 1, newTab);
40
+
41
+ if (activate) {
42
+ this.suspendEvents();
43
+ this.activate(newTab);
44
+ this.resumeEvents();
45
+ }
46
+ },
47
+
48
+ getQuery: function(all) {
49
+ var query = [];
50
+ this.eachTab(function(i) {
51
+ var q = i.getQuery();
52
+ if (q.length > 0) query.push(i.getQuery(all));
53
+ });
54
+ return query;
55
+ },
56
+
57
+ getTabs: function() {
58
+ var res = [];
59
+ this.items.each(function(i) {
60
+ if (i.title !== "+") { res.push(i); }
61
+ });
62
+ return res;
63
+ },
64
+
65
+ eachTab: function(fn, scope) {
66
+ this.items.each(function(f, i) {
67
+ if (this.items.last() !== f) {
68
+ fn.call(scope || f, f);
69
+ }
70
+ }, this);
71
+ },
72
+
73
+ onClearAll: function() {
74
+ this.removeAllTabs(true);
75
+ this.items.first().onClearAll();
76
+ },
77
+
78
+ onReset: function() {
79
+ this.eachTab(function(t) { t.onReset(); });
80
+ },
81
+
82
+ removeAllTabs: function(exceptLast) {
83
+ this.eachTab(function(t) { if (this.items.getCount() > (exceptLast ? 2 : 1)) {this.remove(t);} }, this);
84
+ },
85
+
86
+ onSavePreset: function(){
87
+ var searchName = this.presetsCombo.getRawValue();
88
+
89
+ if (searchName !== "") {
90
+ var presetsComboStore = this.presetsCombo.getStore();
91
+ var existingPresetIndex = presetsComboStore.find('field2', searchName);
92
+ if (existingPresetIndex !== -1) {
93
+ // overwriting
94
+ Ext.Msg.confirm(this.i18n.overwriteConfirmTitle, String.format(this.i18n.overwriteConfirm, searchName), function(btn, text){
95
+ if (btn == 'yes') {
96
+ var r = presetsComboStore.getAt(existingPresetIndex);
97
+ r.set('field1', this.getQuery(true));
98
+ r.commit();
99
+ this.doSavePreset(searchName);
100
+ }
101
+ }, this);
102
+ } else {
103
+ this.doSavePreset(searchName);
104
+ var r = new presetsComboStore.recordType({field1: this.getQuery(true), field2: searchName});
105
+ presetsComboStore.add(r);
106
+ }
107
+ }
108
+ },
109
+
110
+ doSavePreset: function(name){
111
+ this.savePreset({
112
+ name: name,
113
+ query: Ext.encode(this.getQuery(true))
114
+ });
115
+ },
116
+
117
+ onDeletePreset: function(){
118
+ var searchName = this.presetsCombo.getRawValue();
119
+ if (searchName !== "") {
120
+ Ext.Msg.confirm(this.i18n.deleteConfirmTitle, String.format(this.i18n.overwriteConfirm, searchName), function(btn, text){
121
+ if (btn == 'yes') {
122
+ this.removePresetFromList(searchName);
123
+ this.deletePreset({
124
+ name: searchName
125
+ });
126
+ }
127
+ }, this);
128
+ }
129
+ },
130
+
131
+ onApply: function() {
132
+ this.fireEvent('conditionsupdate', this.getQuery());
133
+ },
134
+
135
+ removePresetFromList: function(name){
136
+ var presetsComboStore = this.presetsCombo.getStore();
137
+ presetsComboStore.removeAt(presetsComboStore.find('field2', name));
138
+ this.presetsCombo.reset();
139
+ }
140
+ }
@@ -1,59 +1,76 @@
1
1
  module Netzke
2
2
  module Basepack
3
- class SearchPanel < FormPanel
4
-
5
- js_properties :header => false,
6
- :bbar => false
7
-
8
- # An override
9
- def normalize_field(f)
10
- f = if f.is_a?(Symbol) || f.is_a?(String)
11
- {:name => f.to_s, :operator => default_operator}
12
- else
13
- search_condition = f[:name]
14
- if search_condition.is_a?(MetaWhere::Column)
15
- {:name => search_condition.column, :operator => search_condition.method}
16
- else
17
- {:name => search_condition.to_s}
18
- end
19
- end
3
+ # == Configuration
4
+ # +load_last_preset+ - on load, tries to load the latest saved preset
5
+ class SearchPanel < Base
20
6
 
21
- f = super(f)
7
+ js_base_class "Ext.form.FormPanel"
22
8
 
23
- f[:disabled] = primary_key_attr?(f)
9
+ js_properties(
10
+ :padding => 5,
11
+ :auto_scroll => true
12
+ )
24
13
 
25
- # Association field
26
- if f[:name].to_s.index("__")
27
- f[:xtype] ||= xtype_for_attr_type(:string)
28
- f[:attr_type] = :string
29
- end
14
+ js_include :condition_field
30
15
 
31
- f[:operator] ||= "gt" if [:datetime, :integer, :date].include?(f[:attr_type])
32
- f[:operator] ||= "eq" if f[:attr_type] == :boolean
33
- f[:operator] ||= default_operator
16
+ js_mixin :search_panel
34
17
 
35
- f[:field_label] = [f[:field_label], f[:operator]].join(" ")
36
- f.merge(:name => [f[:name], f[:operator]].join("__"))
37
- end
18
+ js_property :attribute_operators_map, {
19
+ :integer => [
20
+ ["eq", I18n.t('netzke.basepack.search_panel.equals')],
21
+ ["gt", I18n.t('netzke.basepack.search_panel.greater_than')],
22
+ ["lt", I18n.t('netzke.basepack.search_panel.less_than')]
23
+ ],
24
+ :text => [
25
+ ["contains", I18n.t('netzke.basepack.search_panel.contains')] # same as matches => %string%
26
+ ],
27
+ :string => [
28
+ ["contains", I18n.t('netzke.basepack.search_panel.contains')], # same as matches => %string%
29
+ ["matches", I18n.t('netzke.basepack.search_panel.matches')]
30
+ ],
31
+ :boolean => [
32
+ ["is_any", I18n.t('netzke.basepack.search_panel.is_true')],
33
+ ["is_true", I18n.t('netzke.basepack.search_panel.is_true')],
34
+ ["is_false", I18n.t('netzke.basepack.search_panel.is_false')]
35
+ ],
36
+ :datetime => [
37
+ ["eq", I18n.t('netzke.basepack.search_panel.date_equals')],
38
+ ["gt", I18n.t('netzke.basepack.search_panel.after')],
39
+ ["lt", I18n.t('netzke.basepack.search_panel.before')]
40
+ ]
41
+ }
38
42
 
39
- private
40
- def default_operator
41
- "like"
43
+ # Builds default query search panel, where each field is presented
44
+ def default_query
45
+ data_class.column_names.map do |c|
46
+ column_type = data_class.columns_hash[c].type
47
+ operator = (self.class.js_property(:attribute_operators_map)[column_type] || []).first.try(:fetch, 0) || "matches"
48
+ {:attr => c, :attr_type => column_type, :operator => operator}
42
49
  end
50
+ end
43
51
 
44
- # we need to correct the queries to cut off the condition suffixes, otherwise the FormPanel gets confused
45
- def get_combobox_options(params)
46
- column_name = params[:column]
47
- CONDITIONS.each { |c| column_name.sub!(/_#{c}$/, "") }
48
- super(:column => column_name)
49
- end
52
+ def data_class
53
+ @data_class ||= config[:model].constantize
54
+ end
50
55
 
51
- def attr_type_to_xtype_map
52
- super.merge({
53
- :boolean => :tricheckbox
54
- })
56
+ def js_config
57
+ super.merge(
58
+ :attrs => attributes,
59
+ :attrs_hash => data_class.column_names.inject({}){ |hsh,c| hsh.merge(c => data_class.columns_hash[c].type) },
60
+ :query => (config[:load_last_preset] ? last_preset.try(:fetch, "query") : config[:query]) || []
61
+ )
62
+ end
63
+
64
+ def attributes
65
+ data_class.column_names.map do |name|
66
+ [name, data_class.human_attribute_name(name)]
55
67
  end
68
+ end
69
+
70
+ def last_preset
71
+ (state[:presets] || []).last
72
+ end
56
73
 
57
74
  end
58
75
  end
59
- end
76
+ end
@@ -0,0 +1,153 @@
1
+ Ext.ns("Netzke.classes.Basepack.SearchPanel");
2
+
3
+ Netzke.classes.Basepack.SearchPanel.ConditionField = Ext.extend(Ext.form.CompositeField, {
4
+ hideLabel: true,
5
+
6
+ // Config refinements for the value field
7
+ valueFieldConfigs: {
8
+ string: {
9
+ xtype : 'textfield'
10
+ },
11
+ datetime: {
12
+ xtype : 'datefield'
13
+ },
14
+ integer: {
15
+ xtype: 'numberfield'
16
+ }
17
+ },
18
+
19
+ initComponent: function(){
20
+ // if (!this.attrType) this.attrType = 'string';
21
+ var items = [
22
+ // attribute combo
23
+ {
24
+ xtype : 'combo',
25
+ store: this.ownerCt.attrs,
26
+ allowBlank: true,
27
+ name: this.attr + '_attr',
28
+ ref: 'attrCombo',
29
+ emptyText: 'Attribute',
30
+ triggerAction: 'all',
31
+ value: this.attr ? this.attr.underscore() : "",
32
+ listeners: {
33
+ select: this.onAttributeChange,
34
+ scope: this
35
+ }
36
+ }
37
+ ];
38
+
39
+ if (this.attr) {
40
+
41
+ var operators = this.ownerCt.attributeOperatorsMap[this.attrType] || [[]];
42
+
43
+ if (this.attrType === 'boolean') {
44
+ items.push({
45
+ xtype : 'tricheckbox',
46
+ width: 100,
47
+ name: this.attr + '_value',
48
+ ref: 'valueField',
49
+ checked: this.value
50
+ });
51
+
52
+ items.push({
53
+ flex: 1,
54
+ xtype: 'displayfield'
55
+ });
56
+ } else {
57
+ // operator combo
58
+ items.push(
59
+ {
60
+ width: 100,
61
+ xtype : 'combo',
62
+ fieldLabel: 'Operator',
63
+ store: operators,
64
+ name: this.attr + '_operator',
65
+ value: this.operator || operators[0][0],
66
+ autoSelect: true,
67
+ triggerAction: 'all',
68
+ emptyText: "Operator",
69
+ ref: "operatorCombo"
70
+ }
71
+ );
72
+
73
+ // value field
74
+ items.push(Ext.apply(
75
+ {
76
+ xtype : 'textfield',
77
+ emptyText: "Value",
78
+ flex: 1,
79
+ name: this.attr + '_value',
80
+ ref: 'valueField',
81
+ value: this.value,
82
+ ref: "valueField"
83
+ },
84
+ this.valueFieldConfigs[this.attrType] // refining the config dependent on the attr type
85
+ ));
86
+ }
87
+
88
+ // delete button
89
+ items.push({
90
+ xtype: 'button',
91
+ cls: 'x-btn-icon',
92
+ icon: Netzke.RelativeUrlRoot + "/images/icons/cross.png",
93
+ handler: this.removeSelf,
94
+ scope: this
95
+ });
96
+ }
97
+
98
+ this.items = items;
99
+
100
+ // Why on Earth is this not working? Netzke.classes.Basepack.SearchPanel.ConditionField undefined???
101
+ // Netzke.classes.Basepack.SearchPanel.ConditionField.superclass.initComponent.call(this);
102
+ Ext.form.CompositeField.prototype.initComponent.call(this); // workaround
103
+
104
+ this.addEvents('configured'); // user selects the attribute from the attribute combo
105
+ },
106
+
107
+ isConfigured: function() {
108
+ return !!this.attrCombo.getValue();
109
+ },
110
+
111
+ removeSelf: function(){
112
+ var ownerCt = this.ownerCt;
113
+ this.destroy();
114
+ ownerCt.fireEvent('fieldsnumberchange');
115
+ },
116
+
117
+ onAttributeChange: function(e){
118
+ this.fireEvent('configured');
119
+ this.changeAttribute(e.value.camelize(true));
120
+ },
121
+
122
+ // Dynamically replace self with a field with different attrType
123
+ changeAttribute: function(attr){
124
+ var attrType = this.ownerCt.attrsHash[attr];
125
+ var idx = this.ownerCt.items.indexOf(this);
126
+ var owner = this.ownerCt;
127
+ var newSelf = Ext.create(Ext.apply(this.initialConfig, {name: attr, attrType: attrType, attr: attr.underscore(), ownerCt: this.ownerCt, operator: null}));
128
+ owner.remove(this);
129
+ owner.insert(idx, newSelf);
130
+ owner.doLayout();
131
+ },
132
+
133
+ // Returns true if it should be in the query
134
+ valueIsSet: function(){
135
+ return !!(this.attrCombo.getValue() && (this.attrType === 'boolean' || this.operatorCombo.getValue()) && !Ext.isEmpty(this.valueField.getValue()));
136
+ },
137
+
138
+ // Returns the query object
139
+ buildValue: function(){
140
+ var res = {attr: this.attrCombo.getValue(), value: this.valueField.getValue()};
141
+ if (this.attrType !== 'boolean') {
142
+ res.operator = this.operatorCombo.getValue();
143
+ }
144
+ return res;
145
+ },
146
+
147
+ clearValue: function() {
148
+ this.valueField.reset();
149
+ }
150
+
151
+ });
152
+
153
+ Ext.reg('netzkebasepacknewsearchpanelconditionfield', Netzke.classes.Basepack.SearchPanel.ConditionField);
@@ -0,0 +1,64 @@
1
+ {
2
+ initComponent: function() {
3
+ Netzke.classes.Basepack.SearchPanel.superclass.initComponent.call(this);
4
+ this.buildFormFromQuery(this.query);
5
+
6
+ this.onAddCondition();
7
+
8
+ },
9
+
10
+ // Will probably need to be performance-optimized in the future, as recreating the fields is expensive
11
+ buildFormFromQuery: function(query) {
12
+ this.onClearAll();
13
+ Ext.each(query, function(f){
14
+ this.insert(this.items.length - 1, Ext.apply(f, {xtype: 'netzkebasepacknewsearchpanelconditionfield'}));
15
+ }, this);
16
+ this.doLayout();
17
+ },
18
+
19
+ onAddCondition: function() {
20
+ var condField = Ext.create({xtype: 'netzkebasepacknewsearchpanelconditionfield', ownerCt: this});
21
+ condField.on('configured', function() {
22
+ this.onAddCondition();
23
+ }, this, {single: true});
24
+ this.add(condField);
25
+ this.doLayout();
26
+ this.fireEvent('fieldsnumberchange');
27
+ },
28
+
29
+ onReset: function() {
30
+ this.items.each(function(f){
31
+ if (f.valueField) {f.clearValue();}
32
+ });
33
+ },
34
+
35
+ onClearAll: function() {
36
+ this.eachConfiguredField(function(f) {
37
+ this.remove(f);
38
+ }, this);
39
+
40
+ this.fireEvent('fieldsnumberchange');
41
+ },
42
+
43
+ // Returns each condition field which has attribute selected
44
+ eachConfiguredField: function(fn, scope) {
45
+ this.items.each(function(f, i) {
46
+ if (this.items.last() !== f) {
47
+ fn.call(scope || f, f);
48
+ }
49
+ }, this);
50
+ },
51
+
52
+ // When "all" is "true", also includes the fields with empty values
53
+ getQuery: function(all) {
54
+ var query = [];
55
+ this.eachConfiguredField(function(f){
56
+ if (f.valueIsSet() || all) {
57
+ var cond = f.buildValue();
58
+ if (all) {cond.attrType = f.attrType;}
59
+ query.push(cond);
60
+ }
61
+ });
62
+ return query;
63
+ }
64
+ }